Rewrite chunk system (#8177)
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.
This commit is contained in:
parent
abe53a7eb4
commit
01a13871de
942 changed files with 20131 additions and 2697 deletions
|
@ -0,0 +1,84 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shane Freeder <theboyetronic@gmail.com>
|
||||
Date: Wed, 29 May 2019 04:01:22 +0100
|
||||
Subject: [PATCH] ChunkMapDistance CME
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index 0873134f1f6de0c372ba28b89a20302c9a0115d8..e30893d6cbe3b42338d04453d0f452babeb61d8a 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -73,6 +73,7 @@ public class ChunkHolder {
|
||||
private boolean resendLight;
|
||||
private CompletableFuture<Void> pendingFullStateConfirmation;
|
||||
|
||||
+ boolean isUpdateQueued = false; // Paper
|
||||
private final ChunkMap chunkMap; // Paper
|
||||
|
||||
// Paper start
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index f08089b8672454acf8c2309e850466b335248692..6181f675d7addde30f7018b4cd46fe061a14da51 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -52,7 +52,16 @@ public abstract class DistanceManager {
|
||||
private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
|
||||
private final TickingTracker tickingTicketsTracker = new TickingTracker();
|
||||
private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
|
||||
- final Set<ChunkHolder> chunksToUpdateFutures = Sets.newHashSet();
|
||||
+ // Paper start use a queue, but still keep unique requirement
|
||||
+ public final java.util.Queue<ChunkHolder> pendingChunkUpdates = new java.util.ArrayDeque<ChunkHolder>() {
|
||||
+ @Override
|
||||
+ public boolean add(ChunkHolder o) {
|
||||
+ if (o.isUpdateQueued) return true;
|
||||
+ o.isUpdateQueued = true;
|
||||
+ return super.add(o);
|
||||
+ }
|
||||
+ };
|
||||
+ // Paper end
|
||||
final ChunkTaskPriorityQueueSorter ticketThrottler;
|
||||
final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
|
||||
final ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
|
||||
@@ -127,26 +136,14 @@ public abstract class DistanceManager {
|
||||
;
|
||||
}
|
||||
|
||||
- if (!this.chunksToUpdateFutures.isEmpty()) {
|
||||
- // CraftBukkit start
|
||||
- // Iterate pending chunk updates with protection against concurrent modification exceptions
|
||||
- java.util.Iterator<ChunkHolder> iter = this.chunksToUpdateFutures.iterator();
|
||||
- int expectedSize = this.chunksToUpdateFutures.size();
|
||||
- do {
|
||||
- ChunkHolder playerchunk = iter.next();
|
||||
- iter.remove();
|
||||
- expectedSize--;
|
||||
-
|
||||
- playerchunk.updateFutures(chunkStorage, this.mainThreadExecutor);
|
||||
-
|
||||
- // Reset iterator if set was modified using add()
|
||||
- if (this.chunksToUpdateFutures.size() != expectedSize) {
|
||||
- expectedSize = this.chunksToUpdateFutures.size();
|
||||
- iter = this.chunksToUpdateFutures.iterator();
|
||||
- }
|
||||
- } while (iter.hasNext());
|
||||
- // CraftBukkit end
|
||||
-
|
||||
+ // Paper start
|
||||
+ if (!this.pendingChunkUpdates.isEmpty()) {
|
||||
+ while(!this.pendingChunkUpdates.isEmpty()) {
|
||||
+ ChunkHolder remove = this.pendingChunkUpdates.remove();
|
||||
+ remove.isUpdateQueued = false;
|
||||
+ remove.updateFutures(chunkStorage, this.mainThreadExecutor);
|
||||
+ }
|
||||
+ // Paper end
|
||||
return true;
|
||||
} else {
|
||||
if (!this.ticketsToRelease.isEmpty()) {
|
||||
@@ -471,7 +468,7 @@ public abstract class DistanceManager {
|
||||
if (k != level) {
|
||||
playerchunk = DistanceManager.this.updateChunkScheduling(id, level, playerchunk, k);
|
||||
if (playerchunk != null) {
|
||||
- DistanceManager.this.chunksToUpdateFutures.add(playerchunk);
|
||||
+ DistanceManager.this.pendingChunkUpdates.add(playerchunk);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 21 Mar 2021 11:22:10 -0700
|
||||
Subject: [PATCH] Do not copy visible chunks
|
||||
|
||||
For servers with a lot of chunk holders, copying for each
|
||||
tickDistanceManager call can take up quite a bit in
|
||||
the function. I saw approximately 1/3rd of the function
|
||||
on the copy.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
index 83dc09f6526206690c474b50a7a6e71cefc93ab4..7f76c304f5eb3c2f27b348918588ab67b795b1ba 100644
|
||||
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
@@ -202,19 +202,24 @@ public final class ChunkSystem {
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
|
||||
- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
|
||||
+ if (Bukkit.isPrimaryThread()) {
|
||||
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
|
||||
+ }
|
||||
+ synchronized (level.chunkSource.chunkMap.updatingChunks) {
|
||||
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
|
||||
+ }
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
|
||||
- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
|
||||
+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingValuesCopy();
|
||||
}
|
||||
|
||||
public static int getVisibleChunkHolderCount(final ServerLevel level) {
|
||||
- return level.chunkSource.chunkMap.visibleChunkMap.size();
|
||||
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleMap().size();
|
||||
}
|
||||
|
||||
public static int getUpdatingChunkHolderCount(final ServerLevel level) {
|
||||
- return level.chunkSource.chunkMap.updatingChunkMap.size();
|
||||
+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingMap().size();
|
||||
}
|
||||
|
||||
public static boolean hasAnyChunkHolders(final ServerLevel level) {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 2a9e5fb8164f79b0f9c1cb5497216e51f9df3454..ea27e6b1340a42c675bc68ed75f100569114be7a 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -121,9 +121,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
private static final int MIN_VIEW_DISTANCE = 3;
|
||||
public static final int MAX_VIEW_DISTANCE = 33;
|
||||
public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance();
|
||||
+ // Paper start - Don't copy
|
||||
+ public final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<ChunkHolder> updatingChunks = new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>();
|
||||
+ // Paper end - Don't copy
|
||||
public static final int FORCED_TICKET_LEVEL = 31;
|
||||
- public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap();
|
||||
- public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
|
||||
+ // Paper - Don't copy
|
||||
private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads;
|
||||
public final LongSet entitiesInLevel;
|
||||
public final ServerLevel level;
|
||||
@@ -224,7 +226,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
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(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
||||
- this.visibleChunkMap = this.updatingChunkMap.clone();
|
||||
+ // Paper - don't copy
|
||||
this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
|
||||
this.entitiesInLevel = new LongOpenHashSet();
|
||||
this.toDrop = new LongOpenHashSet();
|
||||
@@ -327,12 +329,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
@Nullable
|
||||
public ChunkHolder getUpdatingChunkIfPresent(long pos) {
|
||||
- return (ChunkHolder) this.updatingChunkMap.get(pos);
|
||||
+ return this.updatingChunks.getUpdating(pos); // Paper - Don't copy
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ChunkHolder getVisibleChunkIfPresent(long pos) {
|
||||
- return (ChunkHolder) this.visibleChunkMap.get(pos);
|
||||
+ // Paper start - Don't copy
|
||||
+ if (Thread.currentThread() == this.level.thread) {
|
||||
+ return this.updatingChunks.getVisible(pos);
|
||||
+ }
|
||||
+ return this.updatingChunks.getVisibleAsync(pos);
|
||||
+ // Paper end - Don't copy
|
||||
}
|
||||
|
||||
protected IntSupplier getChunkQueueLevel(long pos) {
|
||||
@@ -515,7 +522,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
// Paper start
|
||||
holder.onChunkAdd();
|
||||
// Paper end
|
||||
- this.updatingChunkMap.put(pos, holder);
|
||||
+ this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
|
||||
this.modified = true;
|
||||
}
|
||||
|
||||
@@ -592,7 +599,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
|
||||
long j = longiterator.nextLong();
|
||||
- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
|
||||
+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
|
||||
|
||||
if (playerchunk != null) {
|
||||
playerchunk.onChunkRemove(); // Paper
|
||||
@@ -672,7 +679,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
if (!this.modified) {
|
||||
return false;
|
||||
} else {
|
||||
- this.visibleChunkMap = this.updatingChunkMap.clone();
|
||||
+ // Paper start - Don't copy
|
||||
+ synchronized (this.updatingChunks) {
|
||||
+ this.updatingChunks.performUpdates();
|
||||
+ }
|
||||
+ // Paper end - Don't copy
|
||||
+
|
||||
this.modified = false;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,432 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sat, 1 Jun 2019 13:00:55 -0700
|
||||
Subject: [PATCH] Chunk debug command
|
||||
|
||||
Prints all chunk information to a text file into the debug
|
||||
folder in the root server folder. The format is in JSON, and
|
||||
the data format is described in MCUtil#dumpChunks(File)
|
||||
|
||||
The command will output server version and all online players to the
|
||||
file as well. We do not log anything but the location, world and
|
||||
username of the player.
|
||||
|
||||
Also logs the value of these config values (note not all are paper's):
|
||||
- keep spawn loaded value
|
||||
- spawn radius
|
||||
- view distance
|
||||
|
||||
Each chunk has the following logged:
|
||||
- Coordinate
|
||||
- Ticket level & its corresponding state
|
||||
- Whether it is queued for unload
|
||||
- Chunk status (may be unloaded)
|
||||
- All tickets on the chunk
|
||||
|
||||
Example log:
|
||||
https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt
|
||||
|
||||
For references on certain keywords (ticket, status, etc), please see:
|
||||
|
||||
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273
|
||||
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577
|
||||
|
||||
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
index b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7..8e773f522521d2dd6349c87b582a3337b76f161f 100644
|
||||
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.papermc.paper.command;
|
||||
|
||||
+import io.papermc.paper.command.subcommands.ChunkDebugCommand;
|
||||
import io.papermc.paper.command.subcommands.EntityCommand;
|
||||
import io.papermc.paper.command.subcommands.HeapDumpCommand;
|
||||
import io.papermc.paper.command.subcommands.ReloadCommand;
|
||||
@@ -40,6 +41,7 @@ public final class PaperCommand extends Command {
|
||||
commands.put(Set.of("entity"), new EntityCommand());
|
||||
commands.put(Set.of("reload"), new ReloadCommand());
|
||||
commands.put(Set.of("version"), new VersionCommand());
|
||||
+ commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand());
|
||||
|
||||
return commands.entrySet().stream()
|
||||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||||
diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..28a9550449be9a212f054b02e43fbd8a3781efcf
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
|
||||
@@ -0,0 +1,166 @@
|
||||
+package io.papermc.paper.command.subcommands;
|
||||
+
|
||||
+import io.papermc.paper.command.CommandUtil;
|
||||
+import io.papermc.paper.command.PaperSubcommand;
|
||||
+import java.io.File;
|
||||
+import java.time.LocalDateTime;
|
||||
+import java.time.format.DateTimeFormatter;
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.Collections;
|
||||
+import java.util.List;
|
||||
+import java.util.Locale;
|
||||
+import net.minecraft.server.MCUtil;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.server.level.ChunkHolder;
|
||||
+import net.minecraft.server.level.ServerLevel;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.craftbukkit.CraftWorld;
|
||||
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
+import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
+
|
||||
+import static net.kyori.adventure.text.Component.text;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.BLUE;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||
+
|
||||
+@DefaultQualifier(NonNull.class)
|
||||
+public final class ChunkDebugCommand implements PaperSubcommand {
|
||||
+ @Override
|
||||
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
+ switch (subCommand) {
|
||||
+ case "debug" -> this.doDebug(sender, args);
|
||||
+ case "chunkinfo" -> this.doChunkInfo(sender, args);
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
+ switch (subCommand) {
|
||||
+ case "debug" -> {
|
||||
+ if (args.length == 1) {
|
||||
+ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks");
|
||||
+ }
|
||||
+ }
|
||||
+ case "chunkinfo" -> {
|
||||
+ List<String> worldNames = new ArrayList<>();
|
||||
+ worldNames.add("*");
|
||||
+ for (org.bukkit.World world : Bukkit.getWorlds()) {
|
||||
+ worldNames.add(world.getName());
|
||||
+ }
|
||||
+ if (args.length == 1) {
|
||||
+ return CommandUtil.getListMatchingLast(sender, args, worldNames);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return Collections.emptyList();
|
||||
+ }
|
||||
+
|
||||
+ private void doChunkInfo(final CommandSender sender, final String[] args) {
|
||||
+ List<org.bukkit.World> worlds;
|
||||
+ if (args.length < 1 || args[0].equals("*")) {
|
||||
+ worlds = Bukkit.getWorlds();
|
||||
+ } else {
|
||||
+ worlds = new ArrayList<>(args.length);
|
||||
+ for (final String arg : args) {
|
||||
+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg);
|
||||
+ if (world == null) {
|
||||
+ sender.sendMessage(text("World '" + arg + "' is invalid", RED));
|
||||
+ return;
|
||||
+ }
|
||||
+ worlds.add(world);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ int accumulatedTotal = 0;
|
||||
+ int accumulatedInactive = 0;
|
||||
+ int accumulatedBorder = 0;
|
||||
+ int accumulatedTicking = 0;
|
||||
+ int accumulatedEntityTicking = 0;
|
||||
+
|
||||
+ for (final org.bukkit.World bukkitWorld : worlds) {
|
||||
+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
|
||||
+
|
||||
+ int total = 0;
|
||||
+ int inactive = 0;
|
||||
+ int border = 0;
|
||||
+ int ticking = 0;
|
||||
+ int entityTicking = 0;
|
||||
+
|
||||
+ for (final ChunkHolder chunk : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world)) {
|
||||
+ if (chunk.getFullChunkNowUnchecked() == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ ++total;
|
||||
+
|
||||
+ ChunkHolder.FullChunkStatus state = chunk.getFullStatus();
|
||||
+
|
||||
+ switch (state) {
|
||||
+ case INACCESSIBLE -> ++inactive;
|
||||
+ case BORDER -> ++border;
|
||||
+ case TICKING -> ++ticking;
|
||||
+ case ENTITY_TICKING -> ++entityTicking;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ accumulatedTotal += total;
|
||||
+ accumulatedInactive += inactive;
|
||||
+ accumulatedBorder += border;
|
||||
+ accumulatedTicking += ticking;
|
||||
+ accumulatedEntityTicking += entityTicking;
|
||||
+
|
||||
+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":")));
|
||||
+ sender.sendMessage(text().color(DARK_AQUA).append(
|
||||
+ text("Total: ", BLUE), text(total),
|
||||
+ text(" Inactive: ", BLUE), text(inactive),
|
||||
+ text(" Border: ", BLUE), text(border),
|
||||
+ text(" Ticking: ", BLUE), text(ticking),
|
||||
+ text(" Entity: ", BLUE), text(entityTicking)
|
||||
+ ));
|
||||
+ }
|
||||
+ if (worlds.size() > 1) {
|
||||
+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA)));
|
||||
+ sender.sendMessage(text().color(DARK_AQUA).append(
|
||||
+ text("Total: ", BLUE), text(accumulatedTotal),
|
||||
+ text(" Inactive: ", BLUE), text(accumulatedInactive),
|
||||
+ text(" Border: ", BLUE), text(accumulatedBorder),
|
||||
+ text(" Ticking: ", BLUE), text(accumulatedTicking),
|
||||
+ text(" Entity: ", BLUE), text(accumulatedEntityTicking)
|
||||
+ ));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void doDebug(final CommandSender sender, final String[] args) {
|
||||
+ if (args.length < 1) {
|
||||
+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ final String debugType = args[0].toLowerCase(Locale.ENGLISH);
|
||||
+ switch (debugType) {
|
||||
+ case "chunks" -> {
|
||||
+ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) {
|
||||
+ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED));
|
||||
+ break;
|
||||
+ }
|
||||
+ File file = new File(new File(new File("."), "debug"),
|
||||
+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
|
||||
+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN));
|
||||
+ try {
|
||||
+ MCUtil.dumpChunks(file);
|
||||
+ sender.sendMessage(text("Successfully written chunk information!", GREEN));
|
||||
+ } catch (Throwable thr) {
|
||||
+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
|
||||
+ sender.sendMessage(text("Failed to dump chunk information, see console", RED));
|
||||
+ }
|
||||
+ }
|
||||
+ // "help" & default
|
||||
+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
index b310d51b7fe3e8cef0a450674725969fe1ce78a4..2e56c52e3ee45b0304a9e6a5eab863ef96b2aab0 100644
|
||||
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
||||
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
@@ -1,15 +1,27 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
+import com.google.gson.JsonArray;
|
||||
+import com.google.gson.JsonObject;
|
||||
+import com.google.gson.internal.Streams;
|
||||
+import com.google.gson.stream.JsonWriter;
|
||||
+import com.mojang.datafixers.util.Either;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
|
||||
import java.lang.ref.Cleaner;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
+import net.minecraft.server.level.ChunkHolder;
|
||||
+import net.minecraft.server.level.ChunkMap;
|
||||
+import net.minecraft.server.level.DistanceManager;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
+import net.minecraft.server.level.ServerPlayer;
|
||||
+import net.minecraft.server.level.Ticket;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
+import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
+import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.BlockFace;
|
||||
@@ -19,8 +31,11 @@ import org.spigotmc.AsyncCatcher;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
+import java.io.*;
|
||||
+import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
+import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -505,6 +520,163 @@ public final class MCUtil {
|
||||
}
|
||||
}
|
||||
|
||||
+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
|
||||
+ return chunk.getChunkHolderStatus();
|
||||
+ }
|
||||
+
|
||||
+ public static void dumpChunks(File file) throws IOException {
|
||||
+ file.getParentFile().mkdirs();
|
||||
+ file.createNewFile();
|
||||
+ /*
|
||||
+ * Json format:
|
||||
+ *
|
||||
+ * Main data format:
|
||||
+ * -server-version:<string>
|
||||
+ * -data-version:<int>
|
||||
+ * -worlds:
|
||||
+ * -name:<world name>
|
||||
+ * -view-distance:<int>
|
||||
+ * -keep-spawn-loaded:<boolean>
|
||||
+ * -keep-spawn-loaded-range:<int>
|
||||
+ * -visible-chunk-count:<int>
|
||||
+ * -loaded-chunk-count:<int>
|
||||
+ * -verified-fully-loaded-chunks:<int>
|
||||
+ * -players:<array of player>
|
||||
+ * -chunk-data:<array of chunks>
|
||||
+ *
|
||||
+ * Player format:
|
||||
+ * -name:<string>
|
||||
+ * -x:<double>
|
||||
+ * -y:<double>
|
||||
+ * -z:<double>
|
||||
+ *
|
||||
+ * Chunk Format:
|
||||
+ * -x:<integer>
|
||||
+ * -z:<integer>
|
||||
+ * -ticket-level:<integer>
|
||||
+ * -state:<string>
|
||||
+ * -queued-for-unload:<boolean>
|
||||
+ * -status:<string>
|
||||
+ * -tickets:<array of tickets>
|
||||
+ *
|
||||
+ *
|
||||
+ * Ticket format:
|
||||
+ * -ticket-type:<string>
|
||||
+ * -ticket-level:<int>
|
||||
+ * -add-tick:<long>
|
||||
+ * -object-reason:<string> // This depends on the type of ticket. ie POST_TELEPORT -> entity id
|
||||
+ */
|
||||
+ List<org.bukkit.World> worlds = org.bukkit.Bukkit.getWorlds();
|
||||
+ JsonObject data = new JsonObject();
|
||||
+
|
||||
+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion());
|
||||
+ data.addProperty("data-version", 0);
|
||||
+
|
||||
+ JsonArray worldsData = new JsonArray();
|
||||
+
|
||||
+ for (org.bukkit.World bukkitWorld : worlds) {
|
||||
+ JsonObject worldData = new JsonObject();
|
||||
+
|
||||
+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
|
||||
+ ChunkMap chunkMap = world.getChunkSource().chunkMap;
|
||||
+ DistanceManager chunkMapDistance = chunkMap.distanceManager;
|
||||
+ List<ChunkHolder> allChunks = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world);
|
||||
+ List<ServerPlayer> players = world.players;
|
||||
+
|
||||
+ int fullLoadedChunks = 0;
|
||||
+
|
||||
+ for (ChunkHolder chunk : allChunks) {
|
||||
+ if (chunk.getFullChunkNowUnchecked() != null) {
|
||||
+ ++fullLoadedChunks;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // sorting by coordinate makes the log easier to read
|
||||
+ allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> {
|
||||
+ if (v1.pos.x != v2.pos.x) {
|
||||
+ return Integer.compare(v1.pos.x, v2.pos.x);
|
||||
+ }
|
||||
+ return Integer.compare(v1.pos.z, v2.pos.z);
|
||||
+ });
|
||||
+
|
||||
+ worldData.addProperty("name", world.getWorld().getName());
|
||||
+ worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
|
||||
+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
|
||||
+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
|
||||
+ worldData.addProperty("visible-chunk-count", allChunks.size());
|
||||
+ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size());
|
||||
+ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks);
|
||||
+
|
||||
+ JsonArray playersData = new JsonArray();
|
||||
+
|
||||
+ for (ServerPlayer player : players) {
|
||||
+ JsonObject playerData = new JsonObject();
|
||||
+
|
||||
+ playerData.addProperty("name", player.getScoreboardName());
|
||||
+ playerData.addProperty("x", player.getX());
|
||||
+ playerData.addProperty("y", player.getY());
|
||||
+ playerData.addProperty("z", player.getZ());
|
||||
+
|
||||
+ playersData.add(playerData);
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ worldData.add("players", playersData);
|
||||
+
|
||||
+ JsonArray chunksData = new JsonArray();
|
||||
+
|
||||
+ for (ChunkHolder playerChunk : allChunks) {
|
||||
+ JsonObject chunkData = new JsonObject();
|
||||
+
|
||||
+ Set<Ticket<?>> tickets = chunkMapDistance.tickets.get(playerChunk.pos.longKey);
|
||||
+ ChunkStatus status = getChunkStatus(playerChunk);
|
||||
+
|
||||
+ chunkData.addProperty("x", playerChunk.pos.x);
|
||||
+ chunkData.addProperty("z", playerChunk.pos.z);
|
||||
+ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
|
||||
+ chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString());
|
||||
+ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey));
|
||||
+ chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
|
||||
+
|
||||
+ JsonArray ticketsData = new JsonArray();
|
||||
+
|
||||
+ if (tickets != null) {
|
||||
+ for (Ticket<?> ticket : tickets) {
|
||||
+ JsonObject ticketData = new JsonObject();
|
||||
+
|
||||
+ ticketData.addProperty("ticket-type", ticket.getType().toString());
|
||||
+ ticketData.addProperty("ticket-level", ticket.getTicketLevel());
|
||||
+ ticketData.addProperty("object-reason", String.valueOf(ticket.key));
|
||||
+ ticketData.addProperty("add-tick", ticket.createdTick);
|
||||
+
|
||||
+ ticketsData.add(ticketData);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ chunkData.add("tickets", ticketsData);
|
||||
+ chunksData.add(chunkData);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ worldData.add("chunk-data", chunksData);
|
||||
+ worldsData.add(worldData);
|
||||
+ }
|
||||
+
|
||||
+ data.add("worlds", worldsData);
|
||||
+
|
||||
+ StringWriter stringWriter = new StringWriter();
|
||||
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
+ jsonWriter.setIndent(" ");
|
||||
+ jsonWriter.setLenient(false);
|
||||
+ Streams.write(data, jsonWriter);
|
||||
+
|
||||
+ String fileData = stringWriter.toString();
|
||||
+
|
||||
+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) {
|
||||
+ out.print(fileData);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
|
||||
return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Fri, 24 Apr 2020 09:06:15 -0700
|
||||
Subject: [PATCH] Make CallbackExecutor strict again
|
||||
|
||||
The correct fix for double scheduling is to avoid it. The reason
|
||||
this class is used is because double scheduling causes issues
|
||||
elsewhere, and it acts as an explicit detector of what double
|
||||
schedules. Effectively, use the callback executor as a tool of
|
||||
finding issues rather than hiding these issues.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index ea27e6b1340a42c675bc68ed75f100569114be7a..4da0cbe58dad0f66e0d056c71684120514dcac6a 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -157,17 +157,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
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<>();
|
||||
+ private Runnable queued; // Paper - revert CB changes
|
||||
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
- this.queue.add(runnable);
|
||||
+ // Paper start - revert CB changes
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute");
|
||||
+ if (this.queued != null) {
|
||||
+ LOGGER.error("Failed to schedule runnable", new IllegalStateException("Already queued"));
|
||||
+ throw new IllegalStateException("Already queued");
|
||||
+ }
|
||||
+ this.queued = runnable;
|
||||
+ // Paper end - revert CB changes
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
- Runnable task;
|
||||
- while ((task = this.queue.poll()) != null) {
|
||||
+ // Paper start - revert CB changes
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute");
|
||||
+ Runnable task = this.queued;
|
||||
+ if (task != null) {
|
||||
+ this.queued = null;
|
||||
+ // Paper end - revert CB changes
|
||||
task.run();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,112 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Zach Brown <zach@zachbr.io>
|
||||
Date: Mon, 6 May 2019 01:29:25 -0400
|
||||
Subject: [PATCH] Per-Player View Distance API placeholders
|
||||
|
||||
I hope to look at this more in-depth soon. It appears doable.
|
||||
However this should not block the update.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
index db7e2207612b56b0869a947edd03a6d3f9209e22..981e60c7bf2eee52e84f9894ff689631388a7715 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -2253,4 +2253,6 @@ public class ServerPlayer extends Player {
|
||||
return (CraftPlayer) super.getBukkitEntity();
|
||||
}
|
||||
// CraftBukkit end
|
||||
+
|
||||
+ public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
|
||||
}
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
index 9a6820b10e4164cc38d269853b5c2a49175cb890..0ed46cdd443ac42a7d57ee59f6f04fd9e9259c16 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
@@ -1913,6 +1913,37 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||
return world.spigotConfig.simulationDistance;
|
||||
}
|
||||
// Spigot end
|
||||
+ // Paper start - view distance api
|
||||
+ @Override
|
||||
+ public void setViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setSimulationDistance(int simulationDistance) {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getNoTickViewDistance() {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setNoTickViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getSendViewDistance() {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setSendViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException(); //TODO
|
||||
+ }
|
||||
+ // Paper end - view distance api
|
||||
|
||||
// Spigot start
|
||||
private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
index 9d2506a042b49089094be79b5d0ed54f088b9625..f2ada37115392466edbed8a2d331084aaaf7b774 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
@@ -408,6 +408,46 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
||||
connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message);
|
||||
}
|
||||
}
|
||||
+
|
||||
+ @Override
|
||||
+ public int getViewDistance() {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getSimulationDistance() {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setSimulationDistance(int simulationDistance) {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getNoTickViewDistance() {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setNoTickViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getSendViewDistance() {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setSendViewDistance(int viewDistance) {
|
||||
+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
+ }
|
||||
// Paper end
|
||||
|
||||
@Override
|
|
@ -0,0 +1,36 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Brokkonaut <hannos17@gmx.de>
|
||||
Date: Tue, 7 Feb 2017 16:55:35 -0600
|
||||
Subject: [PATCH] Make targetSize more aggressive in the chunk unload queue
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 4af8cee31d20e5dcec510439795e7e90fc668128..e086135936e4f6c109cd09a4e4df350702b3510a 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -247,7 +247,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
this.entityMap = new Int2ObjectOpenHashMap();
|
||||
this.chunkTypeCache = new Long2ByteOpenHashMap();
|
||||
this.chunkSaveCooldowns = new Long2LongOpenHashMap();
|
||||
- this.unloadQueue = Queues.newConcurrentLinkedQueue();
|
||||
+ this.unloadQueue = new com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue<>(); // Paper - need constant-time size()
|
||||
this.structureTemplateManager = structureTemplateManager;
|
||||
Path path = session.getDimensionPath(world.dimension());
|
||||
|
||||
@@ -674,7 +674,6 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
private void processUnloads(BooleanSupplier shouldKeepTicking) {
|
||||
LongIterator longiterator = this.toDrop.iterator();
|
||||
-
|
||||
for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
|
||||
long j = longiterator.nextLong();
|
||||
ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
|
||||
@@ -688,7 +687,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
}
|
||||
|
||||
- int k = Math.max(0, this.unloadQueue.size() - 2000);
|
||||
+ int k = Math.max(100, this.unloadQueue.size() - 2000); // Paper - Unload more than just up to queue size 2000
|
||||
|
||||
Runnable runnable;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Mon, 13 May 2019 21:10:59 -0700
|
||||
Subject: [PATCH] Fix CraftServer#isPrimaryThread and MinecraftServer
|
||||
isMainThread
|
||||
|
||||
md_5 changed it so he could shut down the server asynchronously
|
||||
from watchdog, although we have patches that prevent that type
|
||||
of behavior for this exact reason.
|
||||
|
||||
md_5 also placed code in PlayerConnectionUtils that would have
|
||||
solved https://bugs.mojang.com/browse/MC-142590, making the change
|
||||
to MinecraftServer#isMainThread irrelevant.
|
||||
By reverting his change to MinecraftServer#isMainThread packet
|
||||
handling that should have been handled synchronously will be handled
|
||||
synchronously when the server gets shut down.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index 1d554a45097cdf0640788bb796b983f18af31a9f..f7d7e69e29f217c233869951d7d3188816f8216c 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -2327,7 +2327,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
// CraftBukkit start
|
||||
@Override
|
||||
public boolean isSameThread() {
|
||||
- return super.isSameThread() || this.isStopped(); // CraftBukkit - MC-142590
|
||||
+ return super.isSameThread() /*|| this.isStopped()*/; // CraftBukkit - MC-142590 // Paper - causes issues elsewhere
|
||||
}
|
||||
|
||||
public boolean isDebugging() {
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
index fec355dfc7e6353759276f82e6677fd9607e6e7c..2d6f8032cd5a5e7542a4a1cd791852873c93657e 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
@@ -2075,7 +2075,7 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryThread() {
|
||||
- return Thread.currentThread().equals(console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog)
|
||||
+ return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly
|
||||
}
|
||||
|
||||
// Paper start
|
|
@ -0,0 +1,186 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Thu, 7 May 2020 19:17:36 -0400
|
||||
Subject: [PATCH] Fix Light Command
|
||||
|
||||
This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
|
||||
fix all light data in the chunks.
|
||||
|
||||
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644
|
||||
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||||
@@ -2,6 +2,7 @@ package io.papermc.paper.command;
|
||||
|
||||
import io.papermc.paper.command.subcommands.ChunkDebugCommand;
|
||||
import io.papermc.paper.command.subcommands.EntityCommand;
|
||||
+import io.papermc.paper.command.subcommands.FixLightCommand;
|
||||
import io.papermc.paper.command.subcommands.HeapDumpCommand;
|
||||
import io.papermc.paper.command.subcommands.ReloadCommand;
|
||||
import io.papermc.paper.command.subcommands.VersionCommand;
|
||||
@@ -42,6 +43,7 @@ public final class PaperCommand extends Command {
|
||||
commands.put(Set.of("reload"), new ReloadCommand());
|
||||
commands.put(Set.of("version"), new VersionCommand());
|
||||
commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand());
|
||||
+ commands.put(Set.of("fixlight"), new FixLightCommand());
|
||||
|
||||
return commands.entrySet().stream()
|
||||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||||
diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
|
||||
@@ -0,0 +1,121 @@
|
||||
+package io.papermc.paper.command.subcommands;
|
||||
+
|
||||
+import io.papermc.paper.command.PaperSubcommand;
|
||||
+import java.util.ArrayDeque;
|
||||
+import java.util.Deque;
|
||||
+import net.minecraft.server.MCUtil;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.server.level.ChunkHolder;
|
||||
+import net.minecraft.server.level.ServerLevel;
|
||||
+import net.minecraft.server.level.ServerPlayer;
|
||||
+import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||
+import net.minecraft.world.level.ChunkPos;
|
||||
+import net.minecraft.world.level.chunk.LevelChunk;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
+import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
+
|
||||
+import static net.kyori.adventure.text.Component.text;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||
+
|
||||
+@DefaultQualifier(NonNull.class)
|
||||
+public final class FixLightCommand implements PaperSubcommand {
|
||||
+ @Override
|
||||
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
+ this.doFixLight(sender, args);
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ private void doFixLight(final CommandSender sender, final String[] args) {
|
||||
+ if (!(sender instanceof Player)) {
|
||||
+ sender.sendMessage(text("Only players can use this command", RED));
|
||||
+ return;
|
||||
+ }
|
||||
+ @Nullable Runnable post = null;
|
||||
+ int radius = 2;
|
||||
+ if (args.length > 0) {
|
||||
+ try {
|
||||
+ final int parsed = Integer.parseInt(args[0]);
|
||||
+ if (parsed < 0) {
|
||||
+ sender.sendMessage(text("Radius cannot be negative!", RED));
|
||||
+ return;
|
||||
+ }
|
||||
+ final int maxRadius = 5;
|
||||
+ radius = Math.min(maxRadius, parsed);
|
||||
+ if (radius != parsed) {
|
||||
+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED));
|
||||
+ }
|
||||
+ } catch (final Exception e) {
|
||||
+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED));
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ CraftPlayer player = (CraftPlayer) sender;
|
||||
+ ServerPlayer handle = player.getHandle();
|
||||
+ ServerLevel world = (ServerLevel) handle.level;
|
||||
+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine();
|
||||
+
|
||||
+ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation());
|
||||
+ Deque<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
|
||||
+ updateLight(sender, world, lightengine, queue, post);
|
||||
+ }
|
||||
+
|
||||
+ private void updateLight(
|
||||
+ final CommandSender sender,
|
||||
+ final ServerLevel world,
|
||||
+ final ThreadedLevelLightEngine lightengine,
|
||||
+ final Deque<ChunkPos> queue,
|
||||
+ final @Nullable Runnable done
|
||||
+ ) {
|
||||
+ @Nullable ChunkPos coord = queue.poll();
|
||||
+ if (coord == null) {
|
||||
+ sender.sendMessage(text("All Chunks Light updated", GREEN));
|
||||
+ if (done != null) {
|
||||
+ done.run();
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
|
||||
+ if (ex != null) {
|
||||
+ sender.sendMessage(text("Error loading chunk " + coord, RED));
|
||||
+ updateLight(sender, world, lightengine, queue, done);
|
||||
+ return;
|
||||
+ }
|
||||
+ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
|
||||
+ if (chunk == null) {
|
||||
+ updateLight(sender, world, lightengine, queue, done);
|
||||
+ return;
|
||||
+ }
|
||||
+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
|
||||
+ sender.sendMessage(text("Updating Light " + coord));
|
||||
+ int cx = chunk.getPos().x << 4;
|
||||
+ int cz = chunk.getPos().z << 4;
|
||||
+ for (int y = 0; y < world.getHeight(); y++) {
|
||||
+ for (int x = 0; x < 16; x++) {
|
||||
+ for (int z = 0; z < 16; z++) {
|
||||
+ net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z);
|
||||
+ lightengine.checkBlock(pos);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ lightengine.tryScheduleUpdate();
|
||||
+ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey);
|
||||
+ if (visibleChunk != null) {
|
||||
+ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> {
|
||||
+ MinecraftServer.getServer().processQueue.add(() -> {
|
||||
+ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false);
|
||||
+ updateLight(sender, world, lightengine, queue, done);
|
||||
+ });
|
||||
+ });
|
||||
+ } else {
|
||||
+ updateLight(sender, world, lightengine, queue, done);
|
||||
+ }
|
||||
+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize);
|
||||
+ }, MinecraftServer.getServer());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 4b24e4d947e96ea0720f8f6bc33470e07c00310d..d60173b03baee4a66da1109795bf6a19737b8bd0 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -141,6 +141,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
private final ChunkTaskPriorityQueueSorter queueSorter;
|
||||
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
|
||||
public final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
|
||||
+ // Paper start
|
||||
+ final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mailboxLight;
|
||||
+ public void addLightTask(ChunkHolder playerchunk, Runnable run) {
|
||||
+ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run));
|
||||
+ }
|
||||
+ // Paper end
|
||||
public final ChunkProgressListener progressListener;
|
||||
private final ChunkStatusUpdateListener chunkStatusListener;
|
||||
public final ChunkMap.ChunkDistanceManager distanceManager;
|
||||
@@ -284,11 +290,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
this.progressListener = worldGenerationProgressListener;
|
||||
this.chunkStatusListener = chunkStatusChangeListener;
|
||||
- ProcessorMailbox<Runnable> threadedmailbox1 = ProcessorMailbox.create(executor, "light");
|
||||
+ ProcessorMailbox<Runnable> lightthreaded; ProcessorMailbox<Runnable> threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper
|
||||
|
||||
this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE);
|
||||
this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false);
|
||||
this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false);
|
||||
+ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper
|
||||
this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false));
|
||||
this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
|
||||
this.overworldDataStorage = persistentStateManagerFactory;
|
|
@ -0,0 +1,88 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Wed, 15 Apr 2020 18:23:28 -0700
|
||||
Subject: [PATCH] Optimise ArraySetSorted#removeIf
|
||||
|
||||
Remove iterator allocation and ensure the call is always O(n)
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index 9309ea89a440606be3e56ef634f5048a72b0009e..1d1ea158d095bb69260929e8d84f2632a875c136 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -86,13 +86,27 @@ public abstract class DistanceManager {
|
||||
protected void purgeStaleTickets() {
|
||||
++this.ticketTickCounter;
|
||||
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
|
||||
+ // Paper start - use optimised removeIf
|
||||
+ long[] currChunk = new long[1];
|
||||
+ long ticketCounter = DistanceManager.this.ticketTickCounter;
|
||||
+ java.util.function.Predicate<Ticket<?>> removeIf = (ticket) -> {
|
||||
+ final boolean ret = ticket.timedOut(ticketCounter);
|
||||
+ if (ret) {
|
||||
+ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
|
||||
+ }
|
||||
+ return ret;
|
||||
+ };
|
||||
+ // Paper end - use optimised removeIf
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
|
||||
- Iterator<Ticket<?>> iterator = ((SortedArraySet) entry.getValue()).iterator();
|
||||
- boolean flag = false;
|
||||
+ // Paper start - use optimised removeIf
|
||||
+ Iterator<Ticket<?>> iterator = null;
|
||||
+ currChunk[0] = entry.getLongKey();
|
||||
+ boolean flag = entry.getValue().removeIf(removeIf);
|
||||
|
||||
- while (iterator.hasNext()) {
|
||||
+ while (false && iterator.hasNext()) {
|
||||
+ // Paper end - use optimised removeIf
|
||||
Ticket<?> ticket = (Ticket) iterator.next();
|
||||
|
||||
if (ticket.timedOut(this.ticketTickCounter)) {
|
||||
diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
index d1b2ba24ef54e01c6249c3b2ca16e80f03c001a6..5f1c4c6b9e36f2d6ec43b82cc0e2cae24b800dc4 100644
|
||||
--- a/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
+++ b/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
@@ -22,6 +22,41 @@ public class SortedArraySet<T> extends AbstractSet<T> {
|
||||
this.contents = (T[])castRawArray(new Object[initialCapacity]);
|
||||
}
|
||||
}
|
||||
+ // Paper start - optimise removeIf
|
||||
+ @Override
|
||||
+ public boolean removeIf(java.util.function.Predicate<? super T> filter) {
|
||||
+ // prev. impl used an iterator, which could be n^2 and creates garbage
|
||||
+ int i = 0, len = this.size;
|
||||
+ T[] backingArray = this.contents;
|
||||
+
|
||||
+ for (;;) {
|
||||
+ if (i >= len) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (!filter.test(backingArray[i])) {
|
||||
+ ++i;
|
||||
+ continue;
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ // we only want to write back to backingArray if we really need to
|
||||
+
|
||||
+ int lastIndex = i; // this is where new elements are shifted to
|
||||
+
|
||||
+ for (; i < len; ++i) {
|
||||
+ T curr = backingArray[i];
|
||||
+ if (!filter.test(curr)) { // if test throws we're screwed
|
||||
+ backingArray[lastIndex++] = curr;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // cleanup end
|
||||
+ Arrays.fill(backingArray, lastIndex, len, null);
|
||||
+ this.size = lastIndex;
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end - optimise removeIf
|
||||
|
||||
public static <T extends Comparable<T>> SortedArraySet<T> create() {
|
||||
return create(10);
|
|
@ -0,0 +1,73 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Sat, 18 Apr 2020 04:36:11 -0400
|
||||
Subject: [PATCH] Fix Chunk Post Processing deadlock risk
|
||||
|
||||
See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6
|
||||
|
||||
as part of post processing a chunk, we can call ChunkConverter.
|
||||
|
||||
ChunkConverter then kicks off major physics updates, and when blocks
|
||||
that have connections across chunk boundaries occur, a recursive risk
|
||||
can occur where A updates a block that triggers a physics request.
|
||||
|
||||
That physics request may trigger a chunk request, that then enqueues
|
||||
a task into the Mailbox ChunkTaskQueueSorter.
|
||||
|
||||
If anything requests that same chunk that is in the middle of conversion,
|
||||
it's mailbox queue is going to be held up, so the subsequent chunk request
|
||||
will be unable to proceed.
|
||||
|
||||
We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into
|
||||
the executor so that the mailbox ChunkQueue is now considered empty.
|
||||
|
||||
This successfully fixed a reoccurring and highly reproducible crash
|
||||
for heightmaps.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index fcc9dd6e1c54e4ca16102150aa4c12ecc7de06df..aed3da6ef2d498d3f1c9c64177bf1ba6b8157493 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -193,6 +193,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
};
|
||||
// CraftBukkit end
|
||||
|
||||
+ final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper
|
||||
// Paper start - distance maps
|
||||
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
|
||||
|
||||
@@ -1132,16 +1133,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
});
|
||||
CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture1 = completablefuture.thenApplyAsync((either) -> {
|
||||
return either.mapLeft((list) -> {
|
||||
- return (LevelChunk) list.get(list.size() / 2);
|
||||
- });
|
||||
- }, (runnable) -> {
|
||||
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
|
||||
- }).thenApplyAsync((either) -> {
|
||||
- return either.ifLeft((chunk) -> {
|
||||
+ // Paper start - revert 1.18.2 diff
|
||||
+ final LevelChunk chunk = (LevelChunk) list.get(list.size() / 2);
|
||||
chunk.postProcessGeneration();
|
||||
this.level.startTickingChunk(chunk);
|
||||
+ return chunk;
|
||||
});
|
||||
- }, this.mainThreadExecutor);
|
||||
+ }, (runnable) -> {
|
||||
+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request.
|
||||
+ }); // Paper end - revert 1.18.2 diff
|
||||
|
||||
completablefuture1.thenAcceptAsync((either) -> {
|
||||
either.ifLeft((chunk) -> {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 471cc00c677b6581ba84c8cac25d2246c2a14bc9..497827822a64eeff2a4901f0e7c62f0f2c359b48 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -994,6 +994,7 @@ public class ServerChunkCache extends ChunkSource {
|
||||
return super.pollTask() || execChunkTask; // Paper
|
||||
}
|
||||
} finally {
|
||||
+ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter
|
||||
chunkMap.callbackExecutor.run();
|
||||
}
|
||||
// CraftBukkit end
|
|
@ -0,0 +1,69 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Thu, 16 Apr 2020 16:13:59 -0700
|
||||
Subject: [PATCH] Optimize ServerLevels chunk level checking methods
|
||||
|
||||
These can be hot functions (i.e entity ticking and block ticking),
|
||||
so inline where possible, and avoid the abstraction of the
|
||||
Either class.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index a778b4b5b2413c25c2f0f0efc72ba1d362d89acf..c9ecc7593c299b351308634db44596a76fd0c09b 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -2255,19 +2255,22 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
}
|
||||
|
||||
private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
|
||||
- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos);
|
||||
+ // Paper start - optimize is ticking ready type functions
|
||||
+ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
|
||||
+ return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos);
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
public boolean isPositionEntityTicking(BlockPos pos) {
|
||||
- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos));
|
||||
+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); // Paper
|
||||
}
|
||||
|
||||
public boolean isNaturalSpawningAllowed(BlockPos pos) {
|
||||
- return this.entityManager.canPositionTick(pos);
|
||||
+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)); // Paper
|
||||
}
|
||||
|
||||
public boolean isNaturalSpawningAllowed(ChunkPos pos) {
|
||||
- return this.entityManager.canPositionTick(pos);
|
||||
+ return this.entityManager.canPositionTick(pos.toLong()); // Paper
|
||||
}
|
||||
|
||||
private final class EntityCallbacks implements LevelCallback<Entity> {
|
||||
diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java
|
||||
index 2d41f619577b41d6420159668bbab70fb6c762eb..ed0b136e99def41d4377f2004477826b3546a145 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/ChunkPos.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java
|
||||
@@ -60,7 +60,7 @@ public class ChunkPos {
|
||||
}
|
||||
|
||||
public static long asLong(BlockPos pos) {
|
||||
- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
|
||||
+ return (((long)pos.getX() >> 4) & 4294967295L) | ((((long)pos.getZ() >> 4) & 4294967295L) << 32); // Paper - inline
|
||||
}
|
||||
|
||||
public static int getX(long pos) {
|
||||
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
||||
index 16519a6414f6f6418de40b714555a52631980617..a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
||||
@@ -395,6 +395,11 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
||||
public LevelEntityGetter<T> getEntityGetter() {
|
||||
return this.entityGetter;
|
||||
}
|
||||
+ // Paper start
|
||||
+ public final boolean canPositionTick(long position) {
|
||||
+ return this.chunkVisibility.get(position).isTicking();
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
public boolean canPositionTick(BlockPos pos) {
|
||||
return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking();
|
|
@ -0,0 +1,81 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Fri, 29 May 2020 23:32:14 -0400
|
||||
Subject: [PATCH] Improve Chunk Status Transition Speed
|
||||
|
||||
When a chunk is loaded from disk that has already been generated,
|
||||
the server has to promote the chunk through the system to reach
|
||||
it's current desired status level.
|
||||
|
||||
This results in every single status transition going from the main thread
|
||||
to the world gen threads, only to discover it has no work it actually
|
||||
needs to do.... and then it returns back to main.
|
||||
|
||||
This back and forth costs a lot of time and can really delay chunk loads
|
||||
when the server is under high TPS due to their being a lot of time in
|
||||
between chunk load times, as well as hogs up the chunk threads from doing
|
||||
actual generation and light work.
|
||||
|
||||
Additionally, the whole task system uses a lot of CPU on the server threads anyways.
|
||||
|
||||
So by optimizing status transitions for status's that are already complete,
|
||||
we can run them to the desired level while on main thread (where it has
|
||||
to happen anyways) instead of ever jumping to world gen thread.
|
||||
|
||||
This will improve chunk loading effeciency to be reduced down to the following
|
||||
scenario / path:
|
||||
|
||||
1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue
|
||||
2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread
|
||||
3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue
|
||||
4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks)
|
||||
5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task
|
||||
6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done
|
||||
7) MAIN: Task returns to main, finish processing to FULL/TICKING status
|
||||
|
||||
Previously would have hopped to SERVER around 12+ times there extra.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index ac42029596ae0c824bf33a4058ac1009740e29ea..a699107b1afea1c52e5a7e93af8f39ae9e1955b3 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -101,6 +101,13 @@ public class ChunkHolder {
|
||||
// Paper end - optimise anyPlayerCloseEnoughForSpawning
|
||||
long lastAutoSaveTime; // Paper - incremental autosave
|
||||
long inactiveTimeStart; // Paper - incremental autosave
|
||||
+ // Paper start - optimize chunk status progression without jumping through thread pool
|
||||
+ public boolean canAdvanceStatus() {
|
||||
+ ChunkStatus status = getChunkHolderStatus();
|
||||
+ ChunkAccess chunk = getAvailableChunkNow();
|
||||
+ return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status)));
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
|
||||
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 7493da0f1c3f8ab0ebc517347ef23fbe2747a306..5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -726,7 +726,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
return either.mapLeft((list) -> {
|
||||
return (LevelChunk) list.get(list.size() / 2);
|
||||
});
|
||||
- }, this.mainThreadExecutor);
|
||||
+ }, this.mainInvokingExecutor); // Paper
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -1144,6 +1144,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
return "chunkGenerate " + requiredStatus.getName();
|
||||
});
|
||||
Executor executor = (runnable) -> {
|
||||
+ // Paper start - optimize chunk status progression without jumping through thread pool
|
||||
+ if (holder.canAdvanceStatus()) {
|
||||
+ this.mainInvokingExecutor.execute(runnable);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Thu, 11 Mar 2021 02:32:30 -0800
|
||||
Subject: [PATCH] Do not allow the server to unload chunks at request of
|
||||
plugins
|
||||
|
||||
In general the chunk system is not well suited for this behavior,
|
||||
especially if it is called during a chunk load. The chunks pushed
|
||||
to be unloaded will simply be unloaded next tick, rather than
|
||||
immediately.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
index b5f46703e536f8138ff4e6769485c45b35941f9f..f3ab1691948c46477888776d28791ce24e7aa93d 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -696,6 +696,7 @@ public class ServerChunkCache extends ChunkSource {
|
||||
|
||||
// CraftBukkit start - modelled on below
|
||||
public void purgeUnload() {
|
||||
+ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system
|
||||
this.level.getProfiler().push("purge");
|
||||
this.distanceManager.purgeStaleTickets();
|
||||
this.runDistanceManagerUpdates();
|
|
@ -0,0 +1,37 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 21 Mar 2021 17:32:47 -0700
|
||||
Subject: [PATCH] Correctly handle recursion for chunkholder updates
|
||||
|
||||
If a chunk ticket level is brought up while unloading it would
|
||||
cause a recursive call which would handle the increase but then
|
||||
the caller would think the chunk would be unloaded.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index c4046b364d1896b781e23c92b241ec73c239d3a0..9c0bf31c3c362632241c95338a3f8d67bbd4fdc5 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -472,8 +472,10 @@ public class ChunkHolder {
|
||||
playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
|
||||
}
|
||||
|
||||
+ protected long updateCount; // Paper - correctly handle recursion
|
||||
protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
|
||||
io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper
|
||||
+ long updateCount = ++this.updateCount; // Paper - correctly handle recursion
|
||||
ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
|
||||
ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
|
||||
boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
|
||||
@@ -515,6 +517,12 @@ public class ChunkHolder {
|
||||
|
||||
// Run callback right away if the future was already done
|
||||
chunkStorage.callbackExecutor.run();
|
||||
+ // Paper start - correctly handle recursion
|
||||
+ if (this.updateCount != updateCount) {
|
||||
+ // something else updated ticket level for us.
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - correctly handle recursion
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Mon, 1 Feb 2021 15:35:14 -0800
|
||||
Subject: [PATCH] Fix chunks refusing to unload at low TPS
|
||||
|
||||
The full chunk future is appended to the chunk save future, but
|
||||
when moving to unloaded ticket level it is not being completed with
|
||||
the empty chunk access, so the chunk save must wait for the full
|
||||
chunk future to complete. We can simply schedule to the immediate
|
||||
executor to get this effect, rather than the main mailbox.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea..a3fceb2608b3be80941dfe2570999b270429e0c6 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -1352,9 +1352,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
return chunk;
|
||||
});
|
||||
- }, (runnable) -> {
|
||||
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
|
||||
- });
|
||||
+ }, this.mainThreadExecutor); // Paper - queue to execute immediately so this doesn't delay chunk unloading
|
||||
}
|
||||
|
||||
public int getTickingGenerated() {
|
|
@ -0,0 +1,62 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Sat, 19 Sep 2020 15:29:16 -0700
|
||||
Subject: [PATCH] Do not allow ticket level changes while unloading
|
||||
playerchunks
|
||||
|
||||
Sync loading the chunk at this stage would cause it to load
|
||||
older data, as well as screwing our region state.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index a3fceb2608b3be80941dfe2570999b270429e0c6..b34c90497a5492c289839ba74df9f2f27e29370e 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -316,6 +316,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
// Paper end
|
||||
|
||||
+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
|
||||
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(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
||||
// Paper - don't copy
|
||||
@@ -731,6 +732,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
@Nullable
|
||||
ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) {
|
||||
+ if (this.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
|
||||
if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) {
|
||||
return holder;
|
||||
} else {
|
||||
@@ -945,6 +947,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
if (completablefuture1 != completablefuture) {
|
||||
this.scheduleUnload(pos, holder);
|
||||
} else {
|
||||
+ // Paper start - do not allow ticket level changes while unloading chunks
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload");
|
||||
+ boolean unloadingBefore = this.unloadingPlayerChunk;
|
||||
+ this.unloadingPlayerChunk = true;
|
||||
+ try {
|
||||
+ // Paper end - do not allow ticket level changes while unloading chunks
|
||||
// Paper start
|
||||
boolean removed;
|
||||
if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
|
||||
@@ -978,6 +986,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
} else if (removed) { // Paper start
|
||||
net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder);
|
||||
} // Paper end
|
||||
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
|
||||
|
||||
}
|
||||
};
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
index f3ab1691948c46477888776d28791ce24e7aa93d..29ba8971ceffbac68290f6063a69c98065e9bcba 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -632,6 +632,7 @@ public class ServerChunkCache extends ChunkSource {
|
||||
|
||||
public boolean runDistanceManagerUpdates() {
|
||||
if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority
|
||||
+ if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
|
||||
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
|
||||
boolean flag1 = this.chunkMap.promoteChunkMap();
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 20 Jun 2021 00:08:13 -0700
|
||||
Subject: [PATCH] Do not allow ticket level changes when updating chunk ticking
|
||||
state
|
||||
|
||||
This WILL cause state corruption if it happens. So, don't
|
||||
allow it.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index 9c0bf31c3c362632241c95338a3f8d67bbd4fdc5..a2b5f6457b08e4e02544dc71fbf383b5a67a2d69 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -452,7 +452,13 @@ public class ChunkHolder {
|
||||
CompletableFuture<Void> completablefuture1 = new CompletableFuture();
|
||||
|
||||
completablefuture1.thenRunAsync(() -> {
|
||||
+ // Paper start - do not allow ticket level changes
|
||||
+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk;
|
||||
+ this.chunkMap.unloadingPlayerChunk = true;
|
||||
+ try {
|
||||
+ // Paper end - do not allow ticket level changes
|
||||
playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
|
||||
+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes
|
||||
}, executor);
|
||||
this.pendingFullStateConfirmation = completablefuture1;
|
||||
completablefuture.thenAccept((either) -> {
|
||||
@@ -469,7 +475,12 @@ public class ChunkHolder {
|
||||
|
||||
private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) {
|
||||
this.pendingFullStateConfirmation.cancel(false);
|
||||
+ // Paper start - do not allow ticket level changes
|
||||
+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk;
|
||||
+ this.chunkMap.unloadingPlayerChunk = true;
|
||||
+ try { // Paper end - do not allow ticket level changes
|
||||
playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
|
||||
+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes
|
||||
}
|
||||
|
||||
protected long updateCount; // Paper - correctly handle recursion
|
|
@ -0,0 +1,80 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Thu, 18 Jun 2020 18:23:20 -0700
|
||||
Subject: [PATCH] Prevent unload() calls removing tickets for sync loads
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index b2df5e18ce5260a9781052db7afb0b9786fb887c..537d34a0325a985948c744929b90144a66a35ee3 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -545,7 +545,7 @@ public abstract class DistanceManager {
|
||||
}
|
||||
|
||||
public void removeTicketsOnClosing() {
|
||||
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD); // Paper - add additional tickets to preserve
|
||||
+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve
|
||||
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 29ba8971ceffbac68290f6063a69c98065e9bcba..2390fcbc1b21653b1753a58da33f936cec43d0cb 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -537,6 +537,8 @@ public class ServerChunkCache extends ChunkSource {
|
||||
return completablefuture;
|
||||
}
|
||||
|
||||
+ private long syncLoadCounter; // Paper - prevent plugin unloads from removing our ticket
|
||||
+
|
||||
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
|
||||
// Paper start - add isUrgent - old sig left in place for dirty nms plugins
|
||||
return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false);
|
||||
@@ -555,9 +557,12 @@ public class ServerChunkCache extends ChunkSource {
|
||||
ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel());
|
||||
currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER));
|
||||
}
|
||||
+ final Long identifier; // Paper - prevent plugin unloads from removing our ticket
|
||||
if (create && !currentlyUnloading) {
|
||||
// CraftBukkit end
|
||||
this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
|
||||
+ identifier = Long.valueOf(this.syncLoadCounter++); // Paper - prevent plugin unloads from removing our ticket
|
||||
+ this.distanceManager.addTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper - prevent plugin unloads from removing our ticket
|
||||
if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority
|
||||
if (this.chunkAbsent(playerchunk, l)) {
|
||||
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
|
||||
@@ -568,13 +573,21 @@ public class ServerChunkCache extends ChunkSource {
|
||||
playerchunk = this.getVisibleChunkIfPresent(k);
|
||||
gameprofilerfiller.pop();
|
||||
if (this.chunkAbsent(playerchunk, l)) {
|
||||
+ this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper
|
||||
throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
|
||||
}
|
||||
}
|
||||
- }
|
||||
|
||||
+ } else { identifier = null; } // Paper - prevent plugin unloads from removing our ticket
|
||||
// Paper start - Chunk priority
|
||||
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap);
|
||||
+ // Paper start - prevent plugin unloads from removing our ticket
|
||||
+ if (create && !currentlyUnloading) {
|
||||
+ future.thenAcceptAsync((either) -> {
|
||||
+ ServerChunkCache.this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier);
|
||||
+ }, ServerChunkCache.this.mainThreadProcessor);
|
||||
+ }
|
||||
+ // Paper end - prevent plugin unloads from removing our ticket
|
||||
if (isUrgent) {
|
||||
future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair));
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
|
||||
index 3c1698ba0d3bc412ab957777d9b5211dbc555208..41ddcf6775f99c56cf4b13b284420061e5dd6bdc 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/TicketType.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
|
||||
@@ -31,6 +31,7 @@ public class TicketType<T> {
|
||||
public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
|
||||
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
|
||||
public static final TicketType<Long> DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper
|
||||
+ public static final TicketType<Long> REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail
|
||||
|
||||
public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
|
||||
return new TicketType<>(name, argumentComparator, 0L);
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,89 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sat, 19 Jun 2021 22:47:17 -0700
|
||||
Subject: [PATCH] Allow removal/addition of entities to entity ticklist during
|
||||
tick
|
||||
|
||||
It really doesn't make any sense that we would iterate over removed
|
||||
entities during tick. Sure - tick entity checks removed, but
|
||||
does it check if the entity is in an entity ticking chunk?
|
||||
No it doesn't. So, allowing removal while iteration
|
||||
ENSURES only entities MARKED TO TICK are ticked.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
||||
index a176a886235494fdc722030a93658d361bf50f03..4cdfc433df67afcd455422e9baf56f167dd712ae 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
||||
@@ -8,57 +8,42 @@ import javax.annotation.Nullable;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
public class EntityTickList {
|
||||
- private Int2ObjectMap<Entity> active = new Int2ObjectLinkedOpenHashMap<>();
|
||||
- private Int2ObjectMap<Entity> passive = new Int2ObjectLinkedOpenHashMap<>();
|
||||
- @Nullable
|
||||
- private Int2ObjectMap<Entity> iterated;
|
||||
+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
|
||||
|
||||
private void ensureActiveIsNotIterated() {
|
||||
- if (this.iterated == this.active) {
|
||||
- this.passive.clear();
|
||||
-
|
||||
- for(Int2ObjectMap.Entry<Entity> entry : Int2ObjectMaps.fastIterable(this.active)) {
|
||||
- this.passive.put(entry.getIntKey(), entry.getValue());
|
||||
- }
|
||||
-
|
||||
- Int2ObjectMap<Entity> int2ObjectMap = this.active;
|
||||
- this.active = this.passive;
|
||||
- this.passive = int2ObjectMap;
|
||||
- }
|
||||
+ // Paper - replace with better logic, do not delay removals
|
||||
|
||||
}
|
||||
|
||||
public void add(Entity entity) {
|
||||
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
|
||||
this.ensureActiveIsNotIterated();
|
||||
- this.active.put(entity.getId(), entity);
|
||||
+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions
|
||||
}
|
||||
|
||||
public void remove(Entity entity) {
|
||||
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper
|
||||
this.ensureActiveIsNotIterated();
|
||||
- this.active.remove(entity.getId());
|
||||
+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions
|
||||
}
|
||||
|
||||
public boolean contains(Entity entity) {
|
||||
- return this.active.containsKey(entity.getId());
|
||||
+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions
|
||||
}
|
||||
|
||||
public void forEach(Consumer<Entity> action) {
|
||||
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper
|
||||
- if (this.iterated != null) {
|
||||
- throw new UnsupportedOperationException("Only one concurrent iteration supported");
|
||||
- } else {
|
||||
- this.iterated = this.active;
|
||||
-
|
||||
- try {
|
||||
- for(Entity entity : this.active.values()) {
|
||||
- action.accept(entity);
|
||||
- }
|
||||
- } finally {
|
||||
- this.iterated = null;
|
||||
+ // Paper start - replace with better logic, do not delay removals/additions
|
||||
+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
|
||||
+ // (by dfl iterator() is configured to not iterate over new entries)
|
||||
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
|
||||
+ try {
|
||||
+ while (iterator.hasNext()) {
|
||||
+ action.accept(iterator.next());
|
||||
}
|
||||
-
|
||||
+ } finally {
|
||||
+ iterator.finishedIterating();
|
||||
}
|
||||
+ // Paper end - replace with better logic, do not delay removals/additions
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shane Freeder <theboyetronic@gmail.com>
|
||||
Date: Fri, 3 Sep 2021 15:50:25 +0100
|
||||
Subject: [PATCH] Do not process entity loads in CraftChunk#getEntities
|
||||
|
||||
This re-introduces the issue behind #5872 but fixes #6543
|
||||
The logic here is generally flawed however somewhat of a nuance,
|
||||
upstream uses managedBlock which is basically needed to process
|
||||
the posted entity adds, but, has the side-effect of processing any
|
||||
chunk loads which has the naunce of stacking up and either causing a
|
||||
massive performance hit, or can potentially lead the server to crash.
|
||||
|
||||
This issue is particularly noticable on paper due to the cumulative efforts
|
||||
to drastically improve chunk loading speeds which means that there is much more
|
||||
of a chance that we're about to eat a dirtload of chunk load callbacks, thus
|
||||
making this issue much more of an issue
|
||||
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
||||
index 2224b9cfbc3be59037ef49ce278989ea3a710bb5..0b9312dc5ee43d2d450dc6e9f07a9ac0320955ca 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
||||
@@ -130,46 +130,6 @@ public class CraftChunk implements Chunk {
|
||||
this.getWorld().getChunkAt(x, z); // Transient load for this tick
|
||||
}
|
||||
|
||||
- PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = this.getCraftWorld().getHandle().entityManager;
|
||||
- long pair = ChunkPos.asLong(x, z);
|
||||
-
|
||||
- if (entityManager.areEntitiesLoaded(pair)) {
|
||||
- return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
|
||||
- }
|
||||
-
|
||||
- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
|
||||
-
|
||||
- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
|
||||
- ProcessorMailbox<Runnable> mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
|
||||
- BooleanSupplier supplier = () -> {
|
||||
- // only execute inbox if our entities are not present
|
||||
- if (entityManager.areEntitiesLoaded(pair)) {
|
||||
- return true;
|
||||
- }
|
||||
-
|
||||
- if (!entityManager.isPending(pair)) {
|
||||
- // Our entities got unloaded, this should normally not happen.
|
||||
- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
|
||||
- }
|
||||
-
|
||||
- // tick loading inbox, which loads the created entities to the world
|
||||
- // (if present)
|
||||
- entityManager.tick();
|
||||
- // check if our entities are loaded
|
||||
- return entityManager.areEntitiesLoaded(pair);
|
||||
- };
|
||||
-
|
||||
- // now we wait until the entities are loaded,
|
||||
- // the converting from NBT to entity object is done on the main Thread which is why we wait
|
||||
- while (!supplier.getAsBoolean()) {
|
||||
- if (mailbox.size() != 0) {
|
||||
- mailbox.run();
|
||||
- } else {
|
||||
- Thread.yield();
|
||||
- LockSupport.parkNanos("waiting for entity loading", 100000L);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
|
||||
}
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Mon, 31 Aug 2020 11:08:17 -0700
|
||||
Subject: [PATCH] Actually unload POI data
|
||||
|
||||
While it's not likely for a poi data leak to be meaningful,
|
||||
sometimes it is.
|
||||
|
||||
This patch also prevents the saving/unloading of POI data when
|
||||
world saving is disabled.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
index 2a099fe0d514f181bf2b452d5333bc29b0d29e43..81ea64443a843736f9ada97900d173c302e39ba0 100644
|
||||
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
|
||||
@@ -270,6 +270,7 @@ public final class ChunkSystem {
|
||||
for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
|
||||
chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z);
|
||||
}
|
||||
+ chunkMap.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data
|
||||
}
|
||||
|
||||
public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
|
||||
@@ -277,6 +278,7 @@ public final class ChunkSystem {
|
||||
for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
|
||||
chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
|
||||
}
|
||||
+ chunkMap.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
|
||||
}
|
||||
|
||||
public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index fe10c770b511fa8a38ece2bf9679492a85b28eff..a5e74d30045a171f5ed66a115fbd429e9ab412af 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -1016,7 +1016,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
private void processUnloads(BooleanSupplier shouldKeepTicking) {
|
||||
LongIterator longiterator = this.toDrop.iterator();
|
||||
- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
|
||||
+ for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { // Paper - diff on change
|
||||
long j = longiterator.nextLong();
|
||||
ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
|
||||
|
||||
@@ -1164,6 +1164,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
this.poiManager.loadInData(pos, chunkHolder.poiData);
|
||||
chunkHolder.tasks.forEach(Runnable::run);
|
||||
+ this.getPoiManager().dequeueUnload(pos.longKey); // Paper
|
||||
|
||||
if (chunkHolder.protoChunk != null) {
|
||||
ProtoChunk protochunk = chunkHolder.protoChunk;
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
index 497a81e49d54380713c18523ae8f09f94c453721..210b0cdd4831421c8f43c3d823ac8e962b56bbbc 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.minecraft.world.entity.ai.village.poi;
|
||||
|
||||
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ByteMap;
|
||||
@@ -38,16 +39,145 @@ import net.minecraft.world.level.chunk.storage.SectionStorage;
|
||||
public class PoiManager extends SectionStorage<PoiSection> {
|
||||
public static final int MAX_VILLAGE_DISTANCE = 6;
|
||||
public static final int VILLAGE_SECTION_SIZE = 1;
|
||||
- private final PoiManager.DistanceTracker distanceTracker;
|
||||
+ // Paper start - unload poi data
|
||||
+ // the vanilla tracker needs to be replaced because it does not support level removes
|
||||
+ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D();
|
||||
+ static final int POI_DATA_SOURCE = 7;
|
||||
+ public static int convertBetweenLevels(final int level) {
|
||||
+ return POI_DATA_SOURCE - level;
|
||||
+ }
|
||||
+
|
||||
+ protected void updateDistanceTracking(long section) {
|
||||
+ if (this.isVillageCenter(section)) {
|
||||
+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
|
||||
+ } else {
|
||||
+ this.villageDistanceTracker.removeSource(section);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - unload poi data
|
||||
private final LongSet loadedChunks = new LongOpenHashSet();
|
||||
public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
|
||||
|
||||
public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) {
|
||||
super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world);
|
||||
+ if (world == null) { throw new IllegalStateException("world must be non-null"); } // Paper - require non-null
|
||||
this.world = (net.minecraft.server.level.ServerLevel)world; // Paper
|
||||
- this.distanceTracker = new PoiManager.DistanceTracker();
|
||||
}
|
||||
|
||||
+ // Paper start - actually unload POI data
|
||||
+ private final java.util.TreeSet<QueuedUnload> queuedUnloads = new java.util.TreeSet<>();
|
||||
+ private final Long2ObjectOpenHashMap<QueuedUnload> queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>();
|
||||
+
|
||||
+ static final class QueuedUnload implements Comparable<QueuedUnload> {
|
||||
+
|
||||
+ private final long unloadTick;
|
||||
+ private final long coordinate;
|
||||
+
|
||||
+ public QueuedUnload(long unloadTick, long coordinate) {
|
||||
+ this.unloadTick = unloadTick;
|
||||
+ this.coordinate = coordinate;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int compareTo(QueuedUnload other) {
|
||||
+ if (other.unloadTick == this.unloadTick) {
|
||||
+ return Long.compare(this.coordinate, other.coordinate);
|
||||
+ } else {
|
||||
+ return Long.compare(this.unloadTick, other.unloadTick);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int hashCode() {
|
||||
+ int hash = 1;
|
||||
+ hash = hash * 31 + Long.hashCode(this.unloadTick);
|
||||
+ hash = hash * 31 + Long.hashCode(this.coordinate);
|
||||
+ return hash;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean equals(Object obj) {
|
||||
+ if (obj == null || obj.getClass() != QueuedUnload.class) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ QueuedUnload other = (QueuedUnload)obj;
|
||||
+ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ long determineDelay(long coordinate) {
|
||||
+ if (this.isEmpty(coordinate)) {
|
||||
+ return 5 * 60 * 20;
|
||||
+ } else {
|
||||
+ return 60 * 20;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void queueUnload(long coordinate, long minTarget) {
|
||||
+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload queue");
|
||||
+ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate);
|
||||
+ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload);
|
||||
+ if (existing != null) {
|
||||
+ this.queuedUnloads.remove(existing);
|
||||
+ }
|
||||
+ this.queuedUnloads.add(unload);
|
||||
+ }
|
||||
+
|
||||
+ public void dequeueUnload(long coordinate) {
|
||||
+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload dequeue");
|
||||
+ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate);
|
||||
+ if (unload != null) {
|
||||
+ this.queuedUnloads.remove(unload);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void pollUnloads(BooleanSupplier canSleepForTick) {
|
||||
+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload");
|
||||
+ long currentTick = net.minecraft.server.MinecraftServer.currentTickLong;
|
||||
+ net.minecraft.server.level.ServerChunkCache chunkProvider = this.world.getChunkSource();
|
||||
+ net.minecraft.server.level.ChunkMap playerChunkMap = chunkProvider.chunkMap;
|
||||
+ // copied target determination from PlayerChunkMap
|
||||
+
|
||||
+ java.util.Iterator<QueuedUnload> iterator = this.queuedUnloads.iterator();
|
||||
+ for (int i = 0; iterator.hasNext() && (i < 200 || this.queuedUnloads.size() > 2000 || canSleepForTick.getAsBoolean()); i++) {
|
||||
+ QueuedUnload unload = iterator.next();
|
||||
+ if (unload.unloadTick > currentTick) {
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ long coordinate = unload.coordinate;
|
||||
+
|
||||
+ iterator.remove();
|
||||
+ this.queuedUnloadsByCoordinate.remove(coordinate);
|
||||
+
|
||||
+ if (playerChunkMap.getUnloadingChunkHolder(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null
|
||||
+ || playerChunkMap.getUpdatingChunkIfPresent(coordinate) != null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ this.unloadData(coordinate);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void unloadData(long coordinate) {
|
||||
+ io.papermc.paper.util.TickThread.softEnsureTickThread("async unloading poi data");
|
||||
+ super.unloadData(coordinate);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ protected void onUnload(long coordinate) {
|
||||
+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload callback");
|
||||
+ this.loadedChunks.remove(coordinate);
|
||||
+ int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
|
||||
+ int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
|
||||
+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
|
||||
+ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
|
||||
+ this.updateDistanceTracking(sectionPos);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - actually unload POI data
|
||||
+
|
||||
public void add(BlockPos pos, Holder<PoiType> type) {
|
||||
this.getOrCreate(SectionPos.asLong(pos)).add(pos, type);
|
||||
}
|
||||
@@ -201,8 +331,8 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
}
|
||||
|
||||
public int sectionsToVillage(SectionPos pos) {
|
||||
- this.distanceTracker.runAllUpdates();
|
||||
- return this.distanceTracker.getLevel(pos.asLong());
|
||||
+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util
|
||||
+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util
|
||||
}
|
||||
|
||||
boolean isVillageCenter(long pos) {
|
||||
@@ -217,7 +347,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
@Override
|
||||
public void tick(BooleanSupplier shouldKeepTicking) {
|
||||
// Paper start - async chunk io
|
||||
- while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) {
|
||||
+ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean() && !this.world.noSave()) { // Paper - unload POI data - don't write to disk if saving is disabled
|
||||
ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk();
|
||||
|
||||
net.minecraft.nbt.CompoundTag data;
|
||||
@@ -227,19 +357,24 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world,
|
||||
chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
|
||||
}
|
||||
+ // Paper start - unload POI data
|
||||
+ if (!this.world.noSave()) { // don't write to disk if saving is disabled
|
||||
+ this.pollUnloads(shouldKeepTicking);
|
||||
+ }
|
||||
+ // Paper end - unload POI data
|
||||
// Paper end
|
||||
- this.distanceTracker.runAllUpdates();
|
||||
+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking until
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDirty(long pos) {
|
||||
super.setDirty(pos);
|
||||
- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
|
||||
+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSectionLoad(long pos) {
|
||||
- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
|
||||
+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
|
||||
}
|
||||
|
||||
public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) {
|
||||
@@ -297,7 +432,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
||||
|
||||
@Override
|
||||
protected int getLevelFromSource(long id) {
|
||||
- return PoiManager.this.isVillageCenter(id) ? 0 : 7;
|
||||
+ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - unload poi data - diff on change, this specifies the source level to use for distance tracking
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
index 10e8d1e36639cca21aa451e81cdab90ba9e9a496..954819db8ada38ef2c832151be8a96492e76390a 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
||||
@@ -58,6 +58,40 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
|
||||
// Paper - remove mojang I/O thread
|
||||
}
|
||||
|
||||
+ // Paper start - actually unload POI data
|
||||
+ public void unloadData(long coordinate) {
|
||||
+ ChunkPos chunkPos = new ChunkPos(coordinate);
|
||||
+ this.flush(chunkPos);
|
||||
+
|
||||
+ Long2ObjectMap<Optional<R>> data = this.storage;
|
||||
+ int before = data.size();
|
||||
+
|
||||
+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
|
||||
+ data.remove(SectionPos.asLong(chunkPos.x, section, chunkPos.z));
|
||||
+ }
|
||||
+
|
||||
+ if (before != data.size()) {
|
||||
+ this.onUnload(coordinate);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ protected void onUnload(long coordinate) {}
|
||||
+
|
||||
+ public boolean isEmpty(long coordinate) {
|
||||
+ Long2ObjectMap<Optional<R>> data = this.storage;
|
||||
+ int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
|
||||
+ int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
|
||||
+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
|
||||
+ Optional<R> optional = data.get(SectionPos.asLong(x, section, z));
|
||||
+ if (optional != null && optional.orElse(null) != null) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end - actually unload POI data
|
||||
+
|
||||
protected void tick(BooleanSupplier shouldKeepTicking) {
|
||||
while(this.hasWork() && shouldKeepTicking.getAsBoolean()) {
|
||||
ChunkPos chunkPos = SectionPos.of(this.dirty.firstLong()).chunk();
|
||||
@@ -175,6 +209,7 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
|
||||
});
|
||||
}
|
||||
}
|
||||
+ if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 21 Mar 2021 16:25:42 -0700
|
||||
Subject: [PATCH] Replace ticket level propagator
|
||||
|
||||
Mojang's propagator is slow, and this isn't surprising
|
||||
given it's built on the same utilities the vanilla light engine
|
||||
is built on. The simple propagator I wrote is approximately 4x
|
||||
faster when simulating player movement. For a long time timing
|
||||
reports have shown this function take up significant tick, (
|
||||
approx 10% or more), and async sampling data shows the level
|
||||
propagation alone takes up a significant amount. So this
|
||||
should help with that. A big side effect is that mid-tick
|
||||
will be more effective, since more time will be allocated
|
||||
to actually processing chunk tasks vs the ticket level updates.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index 06e4d3a02e0d1326b7029157856476db4ef3575e..f581a9f79b2357118d912a15344ff94df3b0c50e 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper
|
||||
public abstract class DistanceManager {
|
||||
|
||||
static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -48,7 +49,7 @@ public abstract class DistanceManager {
|
||||
private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33;
|
||||
final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
|
||||
public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
|
||||
- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
|
||||
+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
|
||||
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
|
||||
private final TickingTracker tickingTicketsTracker = new TickingTracker();
|
||||
private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
|
||||
@@ -83,6 +84,46 @@ public abstract class DistanceManager {
|
||||
this.chunkMap = chunkMap; // Paper
|
||||
}
|
||||
|
||||
+ // Paper start - replace ticket level propagator
|
||||
+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() {
|
||||
+ @Override
|
||||
+ protected void rehash(int newN) {
|
||||
+ // no downsizing allowed
|
||||
+ if (newN < this.n) {
|
||||
+ return;
|
||||
+ }
|
||||
+ super.rehash(newN);
|
||||
+ }
|
||||
+ };
|
||||
+ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D(
|
||||
+ (long coordinate, byte oldLevel, byte newLevel) -> {
|
||||
+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel));
|
||||
+ }
|
||||
+ );
|
||||
+ // function for converting between ticket levels and propagator levels and vice versa
|
||||
+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects
|
||||
+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator
|
||||
+ // and the levels we get out of the propagator
|
||||
+
|
||||
+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on
|
||||
+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded
|
||||
+ public static int convertBetweenTicketLevels(final int level) {
|
||||
+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1;
|
||||
+ }
|
||||
+
|
||||
+ protected final int getPropagatedTicketLevel(final long coordinate) {
|
||||
+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate));
|
||||
+ }
|
||||
+
|
||||
+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) {
|
||||
+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
|
||||
+ this.ticketLevelPropagator.removeSource(coordinate);
|
||||
+ } else {
|
||||
+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel));
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - replace ticket level propagator
|
||||
+
|
||||
protected void purgeStaleTickets() {
|
||||
++this.ticketTickCounter;
|
||||
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
|
||||
@@ -117,7 +158,7 @@ public abstract class DistanceManager {
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
|
||||
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator
|
||||
}
|
||||
|
||||
if (((SortedArraySet) entry.getValue()).isEmpty()) {
|
||||
@@ -140,61 +181,94 @@ public abstract class DistanceManager {
|
||||
@Nullable
|
||||
protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
|
||||
|
||||
+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
|
||||
public boolean runAllUpdates(ChunkMap chunkStorage) {
|
||||
//this.f.a(); // Paper - no longer used
|
||||
this.tickingTicketsTracker.runAllUpdates();
|
||||
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
|
||||
this.playerTicketManager.runAllUpdates();
|
||||
- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
|
||||
- boolean flag = i != 0;
|
||||
+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
|
||||
|
||||
if (flag) {
|
||||
;
|
||||
}
|
||||
|
||||
- // Paper start
|
||||
- if (!this.pendingChunkUpdates.isEmpty()) {
|
||||
- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority
|
||||
- while(!this.pendingChunkUpdates.isEmpty()) {
|
||||
- ChunkHolder remove = this.pendingChunkUpdates.remove();
|
||||
- remove.isUpdateQueued = false;
|
||||
- remove.updateFutures(chunkStorage, this.mainThreadExecutor);
|
||||
- }
|
||||
- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
|
||||
- // Paper end
|
||||
- return true;
|
||||
- } else {
|
||||
- if (!this.ticketsToRelease.isEmpty()) {
|
||||
- LongIterator longiterator = this.ticketsToRelease.iterator();
|
||||
+ // Paper start - replace level propagator
|
||||
+ ticket_update_loop:
|
||||
+ while (!this.ticketLevelUpdates.isEmpty()) {
|
||||
+ flag = true;
|
||||
|
||||
- while (longiterator.hasNext()) {
|
||||
- long j = longiterator.nextLong();
|
||||
+ boolean oldPolling = this.pollingPendingChunkUpdates;
|
||||
+ this.pollingPendingChunkUpdates = true;
|
||||
+ try {
|
||||
+ for (java.util.Iterator<Long2IntMap.Entry> iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) {
|
||||
+ Long2IntMap.Entry entry = iterator.next();
|
||||
+ long key = entry.getLongKey();
|
||||
+ int newLevel = entry.getIntValue();
|
||||
+ ChunkHolder chunk = this.getChunk(key);
|
||||
+
|
||||
+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
|
||||
+ // not loaded and it shouldn't be loaded!
|
||||
+ continue;
|
||||
+ }
|
||||
|
||||
- if (this.getTickets(j).stream().anyMatch((ticket) -> {
|
||||
- return ticket.getType() == TicketType.PLAYER;
|
||||
- })) {
|
||||
- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j);
|
||||
+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel();
|
||||
|
||||
- if (playerchunk == null) {
|
||||
- throw new IllegalStateException();
|
||||
+ if (currentLevel == newLevel) {
|
||||
+ // nothing to do
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel);
|
||||
+ }
|
||||
+
|
||||
+ long recursiveCheck = ++this.ticketLevelUpdateCount;
|
||||
+ while (!this.ticketLevelUpdates.isEmpty()) {
|
||||
+ long key = this.ticketLevelUpdates.firstLongKey();
|
||||
+ int newLevel = this.ticketLevelUpdates.removeFirstInt();
|
||||
+ ChunkHolder chunk = this.getChunk(key);
|
||||
+
|
||||
+ if (chunk == null) {
|
||||
+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) {
|
||||
+ throw new IllegalStateException("Expected chunk holder to be created");
|
||||
}
|
||||
+ // not loaded and it shouldn't be loaded!
|
||||
+ continue;
|
||||
+ }
|
||||
|
||||
- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getEntityTickingChunkFuture();
|
||||
+ int currentLevel = chunk.oldTicketLevel;
|
||||
|
||||
- completablefuture.thenAccept((either) -> {
|
||||
- this.mainThreadExecutor.execute(() -> {
|
||||
- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
|
||||
- }, j, false));
|
||||
- });
|
||||
- });
|
||||
+ if (currentLevel == newLevel) {
|
||||
+ // nothing to do
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ chunk.updateFutures(chunkStorage, this.mainThreadExecutor);
|
||||
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
|
||||
+ // back to the start, we must create player chunks and update the ticket level fields before
|
||||
+ // processing the actual level updates
|
||||
+ continue ticket_update_loop;
|
||||
}
|
||||
}
|
||||
|
||||
- this.ticketsToRelease.clear();
|
||||
- }
|
||||
+ for (;;) {
|
||||
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
|
||||
+ continue ticket_update_loop;
|
||||
+ }
|
||||
+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll();
|
||||
+ if (pendingUpdate == null) {
|
||||
+ break;
|
||||
+ }
|
||||
|
||||
- return flag;
|
||||
+ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor);
|
||||
+ }
|
||||
+ } finally {
|
||||
+ this.pollingPendingChunkUpdates = oldPolling;
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+ return flag;
|
||||
+ // Paper end - replace level propagator
|
||||
}
|
||||
boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
|
||||
|
||||
@@ -206,7 +280,7 @@ public abstract class DistanceManager {
|
||||
|
||||
ticket1.setCreatedTick(this.ticketTickCounter);
|
||||
if (ticket.getTicketLevel() < j) {
|
||||
- this.ticketTracker.update(i, ticket.getTicketLevel(), true);
|
||||
+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator
|
||||
}
|
||||
|
||||
return ticket == ticket1; // CraftBukkit
|
||||
@@ -250,7 +324,7 @@ public abstract class DistanceManager {
|
||||
// Paper start - Chunk priority
|
||||
int newLevel = getTicketLevelAt(arraysetsorted);
|
||||
if (newLevel > oldLevel) {
|
||||
- this.ticketTracker.update(i, newLevel, false);
|
||||
+ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator
|
||||
}
|
||||
// Paper end
|
||||
return removed; // CraftBukkit
|
||||
@@ -564,7 +638,7 @@ public abstract class DistanceManager {
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
|
||||
+ this.updateTicketLevel(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue())); // Paper - replace ticket level propagator
|
||||
}
|
||||
|
||||
if (((SortedArraySet) entry.getValue()).isEmpty()) {
|
||||
@@ -587,7 +661,7 @@ public abstract class DistanceManager {
|
||||
SortedArraySet<Ticket<?>> tickets = entry.getValue();
|
||||
if (tickets.remove(target)) {
|
||||
// copied from removeTicket
|
||||
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
|
||||
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator
|
||||
|
||||
// can't use entry after it's removed
|
||||
if (tickets.isEmpty()) {
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,74 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sat, 5 Mar 2022 17:12:52 -0800
|
||||
Subject: [PATCH] Fix save problems on shutdown
|
||||
|
||||
- Save level.dat first, in case the shutdown is killed later
|
||||
- Force run minecraftserver tasks and the chunk source tasks
|
||||
while waiting for the chunk system to empty, as there's simply
|
||||
too much trash that could prevent them from executing during
|
||||
the chunk source tick (i.e "time left in tick" logic).
|
||||
- Set forceTicks to true, so that player packets are always
|
||||
processed so that the main process queue can be drained
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index df08b7afcf19ce694a87c25e8589c0c72521c5db..4d920031300a9801debc2eb39a4d3cb9d8fbb330 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -957,6 +957,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
|
||||
+ // Paper start - let's be a little more intelligent around crashes
|
||||
+ // make sure level.dat saves
|
||||
+ for (ServerLevel level : this.getAllLevels()) {
|
||||
+ level.saveLevelDat();
|
||||
+ }
|
||||
+ // Paper end - let's be a little more intelligent around crashes
|
||||
+
|
||||
while (this.levels.values().stream().anyMatch((worldserver1) -> {
|
||||
return worldserver1.getChunkSource().chunkMap.hasWork();
|
||||
})) {
|
||||
@@ -969,9 +976,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
worldserver.getChunkSource().tick(() -> {
|
||||
return true;
|
||||
}, false);
|
||||
+ while (worldserver.getChunkSource().pollTask()); // Paper - drain tasks
|
||||
}
|
||||
|
||||
- this.waitUntilNextTick();
|
||||
+ this.forceTicks = true; // Paper
|
||||
+ while (this.pollTask()); // Paper - drain tasks
|
||||
}
|
||||
|
||||
this.saveAllChunks(false, true, false);
|
||||
@@ -1266,6 +1275,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
private boolean haveTime() {
|
||||
+ // Paper start
|
||||
+ if (this.forceTicks) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end
|
||||
// CraftBukkit start
|
||||
if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
|
||||
return this.forceTicks || this.runningTask() || Util.getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index e71ae32d9827d8a6fb8543abdba7627897ac9f2e..eceaa1f2ede1c068f9090d13bf9d3b3afaa08cc3 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -1284,7 +1284,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
}
|
||||
|
||||
}
|
||||
+ // Paper start
|
||||
+ this.saveLevelDat();
|
||||
+ }
|
||||
|
||||
+ public void saveLevelDat() {
|
||||
+ this.saveLevelData();
|
||||
+ // Paper end
|
||||
// CraftBukkit start - moved from MinecraftServer.saveChunks
|
||||
ServerLevel worldserver1 = this;
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue