p a t c h e s
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: AvrooVulcan <avrovulcan.programming@gmail.com>
|
||||
Date: Fri, 17 Apr 2020 00:15:23 +0100
|
||||
Subject: [PATCH] Broadcast join message to console
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
|
||||
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
|
||||
joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure
|
||||
- this.server.getPlayerList().broadcastAll(new ClientboundChatPacket(joinMessage, ChatType.SYSTEM, Util.NIL_UUID)); // Paper - Adventure
|
||||
+ // Paper start - Removed sendAll for loop and broadcasted to console also
|
||||
+ this.server.getPlayerList().broadcastMessage(joinMessage, ChatType.SYSTEM, Util.NIL_UUID); // Paper - Adventure
|
||||
+ // Paper end
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Sat, 18 Apr 2020 15:59:41 -0400
|
||||
Subject: [PATCH] Don't crash if player is attempted to be removed from
|
||||
untracked chunk.
|
||||
|
||||
I suspect it deals with teleporting as it uses players current x/y/z
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
|
||||
ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
|
||||
if (objectset == null) return; // CraftBukkit - SPIGOT-6208
|
||||
|
||||
- objectset.remove(player);
|
||||
- if (objectset.isEmpty()) {
|
||||
+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully.
|
||||
+ if (objectset == null || objectset.isEmpty()) { // Paper
|
||||
this.playersPerChunk.remove(i);
|
||||
this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
|
||||
this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
|
||||
@@ -1,30 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Wed, 15 Apr 2020 17:56:07 -0700
|
||||
Subject: [PATCH] Don't run entity collision code if not needed
|
||||
|
||||
Will not run if max entity craming is disabled and
|
||||
the max collisions per entity is less than or equal to 0
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity {
|
||||
protected void serverAiStep() {}
|
||||
|
||||
protected void pushEntities() {
|
||||
+ // Paper start - don't run getEntities if we're not going to use its result
|
||||
+ int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
|
||||
+ if (i <= 0 && level.paperConfig.maxCollisionsPerEntity <= 0) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - don't run getEntities if we're not going to use its result
|
||||
List<Entity> list = this.level.getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
- int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
|
||||
+ // Paper - move up
|
||||
int j;
|
||||
|
||||
if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) {
|
||||
@@ -1,152 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: MiniDigger | Martin <admin@minidigger.dev>
|
||||
Date: Mon, 20 Jan 2020 21:38:15 +0100
|
||||
Subject: [PATCH] Implement Player Client Options API
|
||||
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package com.destroystokyo.paper;
|
||||
+
|
||||
+import com.google.common.base.Objects;
|
||||
+
|
||||
+import java.util.StringJoiner;
|
||||
+
|
||||
+public class PaperSkinParts implements SkinParts {
|
||||
+
|
||||
+ private final int raw;
|
||||
+
|
||||
+ public PaperSkinParts(int raw) {
|
||||
+ this.raw = raw;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasCapeEnabled() {
|
||||
+ return (raw & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasJacketEnabled() {
|
||||
+ return (raw >> 1 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasLeftSleeveEnabled() {
|
||||
+ return (raw >> 2 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasRightSleeveEnabled() {
|
||||
+ return (raw >> 3 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasLeftPantsEnabled() {
|
||||
+ return (raw >> 4 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasRightPantsEnabled() {
|
||||
+ return (raw >> 5 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ public boolean hasHatsEnabled() {
|
||||
+ return (raw >> 6 & 1) == 1;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getRaw() {
|
||||
+ return raw;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean equals(Object o) {
|
||||
+ if (this == o) return true;
|
||||
+ if (o == null || getClass() != o.getClass()) return false;
|
||||
+ PaperSkinParts that = (PaperSkinParts) o;
|
||||
+ return raw == that.raw;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int hashCode() {
|
||||
+ return Objects.hashCode(raw);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String toString() {
|
||||
+ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]")
|
||||
+ .add("raw=" + raw)
|
||||
+ .add("cape=" + hasCapeEnabled())
|
||||
+ .add("jacket=" + hasJacketEnabled())
|
||||
+ .add("leftSleeve=" + hasLeftSleeveEnabled())
|
||||
+ .add("rightSleeve=" + hasRightSleeveEnabled())
|
||||
+ .add("leftPants=" + hasLeftPantsEnabled())
|
||||
+ .add("rightPants=" + hasRightPantsEnabled())
|
||||
+ .add("hats=" + hasHatsEnabled())
|
||||
+ .toString();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
|
||||
public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null
|
||||
public java.util.Locale adventure$locale = java.util.Locale.US; // Paper
|
||||
public void updateOptions(ServerboundClientInformationPacket packet) {
|
||||
+ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), packet.language, packet.viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(packet.getChatVisibility().name()), packet.getChatColors(), new com.destroystokyo.paper.PaperSkinParts(packet.getModelCustomisation()), packet.getMainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT).callEvent(); // Paper - settings event
|
||||
// CraftBukkit start
|
||||
if (getMainArm() != packet.getMainHand()) {
|
||||
PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
||||
@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
||||
public void setViewDistance(int viewDistance) {
|
||||
throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
||||
}
|
||||
+
|
||||
+ @Override
|
||||
+ public <T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type) {
|
||||
+ if(com.destroystokyo.paper.ClientOption.SKIN_PARTS.equals(type)) {
|
||||
+ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION)));
|
||||
+ } else if(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED.equals(type)) {
|
||||
+ return type.getType().cast(getHandle().canChatInColor());
|
||||
+ } else if(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY.equals(type)) {
|
||||
+ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name()));
|
||||
+ } else if(com.destroystokyo.paper.ClientOption.LOCALE.equals(type)) {
|
||||
+ return type.getType().cast(getLocale());
|
||||
+ } else if(com.destroystokyo.paper.ClientOption.MAIN_HAND.equals(type)) {
|
||||
+ return type.getType().cast(getMainHand());
|
||||
+ } else if(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE.equals(type)) {
|
||||
+ return type.getType().cast(getClientViewDistance());
|
||||
+ }
|
||||
+ throw new RuntimeException("Unknown settings type");
|
||||
+ }
|
||||
// Paper end
|
||||
|
||||
@Override
|
||||
diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package io.papermc.paper.world;
|
||||
+
|
||||
+import com.destroystokyo.paper.ClientOption;
|
||||
+import net.minecraft.network.chat.TranslatableComponent;
|
||||
+import net.minecraft.world.entity.player.ChatVisiblity;
|
||||
+import org.bukkit.Difficulty;
|
||||
+import org.junit.Assert;
|
||||
+import org.junit.Test;
|
||||
+
|
||||
+public class TranslationKeyTest {
|
||||
+
|
||||
+ @Test
|
||||
+ public void testChatVisibilityKeys() {
|
||||
+ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) {
|
||||
+ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue;
|
||||
+ Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey());
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -1,583 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Sun, 12 Apr 2020 15:50:48 -0400
|
||||
Subject: [PATCH] Improved Watchdog Support
|
||||
|
||||
Forced Watchdog Crash support and Improve Async Shutdown
|
||||
|
||||
If the request to shut down the server is received while we are in
|
||||
a watchdog hang, immediately treat it as a crash and begin the shutdown
|
||||
process. Shutdown process is now improved to also shutdown cleanly when
|
||||
not using restart scripts either.
|
||||
|
||||
If a server is deadlocked, a server owner can send SIGUP (or any other signal
|
||||
the JVM understands to shut down as it currently does) and the watchdog
|
||||
will no longer need to wait until the full timeout, allowing you to trigger
|
||||
a close process and try to shut the server down gracefully, saving player and
|
||||
world data.
|
||||
|
||||
Previously there was no way to trigger this outside of waiting for a full watchdog
|
||||
timeout, which may be set to a really long time...
|
||||
|
||||
Additionally, fix everything to do with shutting the server down asynchronously.
|
||||
|
||||
Previously, nearly everything about the process was fragile and unsafe. Main might
|
||||
not have actually been frozen, and might still be manipulating state.
|
||||
|
||||
Or, some reuest might ask main to do something in the shutdown but main is dead.
|
||||
|
||||
Or worse, other things might start closing down items such as the Console or Thread Pool
|
||||
before we are fully shutdown.
|
||||
|
||||
This change tries to resolve all of these issues by moving everything into the stop
|
||||
method and guaranteeing only one thread is stopping the server.
|
||||
|
||||
We then issue Thread Death to the main thread of another thread initiates the stop process.
|
||||
We have to ensure Thread Death propagates correctly though to stop main completely.
|
||||
|
||||
This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save.
|
||||
|
||||
This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they
|
||||
are properly accounted for and wont trip watchdog on init.
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
|
||||
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
|
||||
@@ -0,0 +0,0 @@ public class Metrics {
|
||||
* Starts the Scheduler which submits our data every 30 minutes.
|
||||
*/
|
||||
private void startSubmitting() {
|
||||
- final Runnable submitTask = this::submitData;
|
||||
+ final Runnable submitTask = () -> {
|
||||
+ if (MinecraftServer.getServer().hasStopped()) {
|
||||
+ return;
|
||||
+ }
|
||||
+ submitData();
|
||||
+ };
|
||||
|
||||
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the
|
||||
// bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay.
|
||||
diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/CrashReport.java
|
||||
+++ b/src/main/java/net/minecraft/CrashReport.java
|
||||
@@ -0,0 +0,0 @@ public class CrashReport {
|
||||
}
|
||||
|
||||
public static CrashReport forThrowable(Throwable cause, String title) {
|
||||
+ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper
|
||||
while (cause instanceof CompletionException && cause.getCause() != null) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
|
||||
public int autosavePeriod;
|
||||
public Commands vanillaCommandDispatcher;
|
||||
- private boolean forceTicks;
|
||||
+ public boolean forceTicks; // Paper
|
||||
// CraftBukkit end
|
||||
// Spigot start
|
||||
public static final int TPS = 20;
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
// Spigot end
|
||||
public static long currentTickLong = 0L; // Paper
|
||||
|
||||
+ public volatile Thread shutdownThread; // Paper
|
||||
+ public volatile boolean abnormalExit = false; // Paper
|
||||
+
|
||||
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
|
||||
AtomicReference<S> atomicreference = new AtomicReference();
|
||||
Thread thread = new Thread(() -> {
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
// CraftBukkit start
|
||||
private boolean hasStopped = false;
|
||||
+ public volatile boolean hasFullyShutdown = false; // Paper
|
||||
private final Object stopLock = new Object();
|
||||
public final boolean hasStopped() {
|
||||
synchronized (this.stopLock) {
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
if (this.hasStopped) return;
|
||||
this.hasStopped = true;
|
||||
}
|
||||
+ // Paper start - kill main thread, and kill it hard
|
||||
+ shutdownThread = Thread.currentThread();
|
||||
+ org.spigotmc.WatchdogThread.doStop(); // Paper
|
||||
+ if (!isSameThread()) {
|
||||
+ MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER)");
|
||||
+ while (this.getRunningThread().isAlive()) {
|
||||
+ this.getRunningThread().stop();
|
||||
+ try {
|
||||
+ Thread.sleep(1);
|
||||
+ } catch (InterruptedException e) {}
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
// CraftBukkit end
|
||||
MinecraftServer.LOGGER.info("Stopping server");
|
||||
MinecraftTimings.stopServer(); // Paper
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.getProfileCache().save(false); // Paper
|
||||
}
|
||||
// Spigot end
|
||||
+ // Paper start - move final shutdown items here
|
||||
+ LOGGER.info("Flushing Chunk IO");
|
||||
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.close(true, true); // Paper
|
||||
+ LOGGER.info("Closing Thread Pool");
|
||||
+ Util.shutdownExecutors(); // Paper
|
||||
+ LOGGER.info("Closing Server");
|
||||
+ try {
|
||||
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
|
||||
+ } catch (Exception e) {
|
||||
+ }
|
||||
+ this.onServerExit();
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
public String getLocalIp() {
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
protected void runServer() {
|
||||
try {
|
||||
+ long serverStartTime = Util.getNanos(); // Paper
|
||||
if (this.initServer()) {
|
||||
this.nextTickTime = Util.getMillis();
|
||||
this.status.setDescription(new TextComponent(this.motd));
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.updateStatusIcon(this.status);
|
||||
|
||||
// Spigot start
|
||||
+ // Paper start - move done tracking
|
||||
+ LOGGER.info("Running delayed init tasks");
|
||||
+ this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // run all 1 tick delay tasks during init,
|
||||
+ // this is going to be the first thing the tick process does anyways, so move done and run it after
|
||||
+ // everything is init before watchdog tick.
|
||||
+ // anything at 3+ won't be caught here but also will trip watchdog....
|
||||
+ // tasks are default scheduled at -1 + delay, and first tick will tick at 1
|
||||
+ String doneTime = String.format(java.util.Locale.ROOT, "%.3fs", (double) (Util.getNanos() - serverStartTime) / 1.0E9D);
|
||||
+ LOGGER.info("Done ({})! For help, type \"help\"", doneTime);
|
||||
+ // Paper end
|
||||
+
|
||||
+ org.spigotmc.WatchdogThread.tick(); // Paper
|
||||
org.spigotmc.WatchdogThread.hasStarted = true; // Paper
|
||||
Arrays.fill( recentTps, 20 );
|
||||
long start = System.nanoTime(), curTime, tickSection = start; // Paper - Further improve server tick loop
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.onServerCrash((CrashReport) null);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
+ // Paper start
|
||||
+ if (throwable instanceof ThreadDeath) {
|
||||
+ MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", throwable);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end
|
||||
MinecraftServer.LOGGER.error("Encountered an unexpected exception", throwable);
|
||||
// Spigot Start
|
||||
if ( throwable.getCause() != null )
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
} catch (Throwable throwable1) {
|
||||
MinecraftServer.LOGGER.error("Exception stopping the server", throwable1);
|
||||
} finally {
|
||||
- org.spigotmc.WatchdogThread.doStop(); // Spigot
|
||||
+ //org.spigotmc.WatchdogThread.doStop(); // Spigot // Paper - move into stop
|
||||
// CraftBukkit start - Restore terminal to original settings
|
||||
try {
|
||||
- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
|
||||
+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// CraftBukkit end
|
||||
- this.onServerExit();
|
||||
+ //this.exit(); // Paper - moved into stop
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
@Override
|
||||
public TickTask wrapRunnable(Runnable runnable) {
|
||||
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
|
||||
+ if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
|
||||
+ runnable.run();
|
||||
+ runnable = () -> {};
|
||||
+ }
|
||||
+ // Paper end
|
||||
return new TickTask(this.tickCount, runnable);
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
try {
|
||||
crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
|
||||
} catch (Throwable t) {
|
||||
+ if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper
|
||||
throw new RuntimeException("Error generating crash report", t);
|
||||
}
|
||||
// Spigot End
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.packRepository.setSelected(datapacks);
|
||||
this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository));
|
||||
datapackresources.updateGlobals();
|
||||
- this.getPlayerList().saveAll();
|
||||
+ if (Thread.currentThread() != this.serverThread) return; // Paper
|
||||
+ //this.getPlayerList().savePlayers(); // Paper - we don't need to do this
|
||||
this.getPlayerList().reloadResources();
|
||||
this.functionManager.replaceLibrary(this.resources.getFunctionLibrary());
|
||||
this.structureManager.onResourceManagerReload(this.resources.getResourceManager());
|
||||
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
||||
@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||
long j = Util.getNanos() - i;
|
||||
String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
|
||||
|
||||
- DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
|
||||
+ //DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); // Paper moved to after init
|
||||
if (dedicatedserverproperties.announcePlayerAchievements != null) {
|
||||
((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, (MinecraftServer) this);
|
||||
}
|
||||
@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||
//this.remoteStatusListener.b(); // Paper - don't wait for remote connections
|
||||
}
|
||||
|
||||
- System.exit(0); // CraftBukkit
|
||||
+ hasFullyShutdown = true; // Paper
|
||||
+ System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||
@Override
|
||||
public void stopServer() {
|
||||
super.stopServer();
|
||||
- Util.shutdownExecutors();
|
||||
+ //SystemUtils.h(); // Paper - moved into super
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
MutableBoolean mutableboolean = new MutableBoolean();
|
||||
|
||||
do {
|
||||
+ boolean isShuttingDown = level.getServer().hasStopped(); // Paper
|
||||
mutableboolean.setFalse();
|
||||
list.stream().map((playerchunk) -> {
|
||||
CompletableFuture completablefuture;
|
||||
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
this.cserver.getPluginManager().callEvent(playerQuitEvent);
|
||||
entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
|
||||
|
||||
- entityplayer.doTick(); // SPIGOT-924
|
||||
+ if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
|
||||
// CraftBukkit end
|
||||
|
||||
// Paper start - Remove from collideRule team if needed
|
||||
diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
||||
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
||||
@@ -0,0 +0,0 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception var3) {
|
||||
+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper
|
||||
LOGGER.fatal("Error executing task on {}", this.name(), var3);
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/Level.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
||||
try {
|
||||
tickConsumer.accept(entity);
|
||||
} catch (Throwable throwable) {
|
||||
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
|
||||
// Paper start - Prevent tile entity and entity crashes
|
||||
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
|
||||
MinecraftServer.LOGGER.error(msg, throwable);
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
|
||||
|
||||
gameprofilerfiller.pop();
|
||||
} catch (Throwable throwable) {
|
||||
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
|
||||
// Paper start - Prevent tile entity and entity crashes
|
||||
final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
|
||||
net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryThread() {
|
||||
- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly
|
||||
+ return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario
|
||||
}
|
||||
|
||||
// Paper start
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||
@@ -0,0 +0,0 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
+import net.minecraft.util.ExceptionCollector;
|
||||
+import net.minecraft.world.level.lighting.LayerLightEventListener;
|
||||
import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper
|
||||
|
||||
public class Main {
|
||||
@@ -0,0 +0,0 @@ public class Main {
|
||||
|
||||
OptionSet options = null;
|
||||
|
||||
+ // Paper start - preload logger classes to avoid plugins mixing versions
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.Core");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.Appender");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.Filter");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.LogEvent");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.Logger");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.Message");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo");
|
||||
+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement");
|
||||
+ // Paper end
|
||||
try {
|
||||
options = parser.parse(args);
|
||||
} catch (joptsimple.OptionException ex) {
|
||||
@@ -0,0 +0,0 @@ public class Main {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
+ // Paper start
|
||||
+ // load some required classes to avoid errors during shutdown if jar is replaced
|
||||
+ // also to guarantee our version loads over plugins
|
||||
+ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow");
|
||||
+ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl");
|
||||
+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values");
|
||||
+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator");
|
||||
+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry");
|
||||
+ tryPreloadClass("com.google.common.collect.Iterables");
|
||||
+ for (int i = 1; i <= 15; i++) {
|
||||
+ tryPreloadClass("com.google.common.collect.Iterables$" + i, false);
|
||||
+ }
|
||||
+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean");
|
||||
+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt");
|
||||
+ tryPreloadClass("org.jline.terminal.impl.MouseSupport");
|
||||
+ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1");
|
||||
+ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking");
|
||||
+ tryPreloadClass("co.aikar.timings.TimingHistory");
|
||||
+ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport");
|
||||
+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext");
|
||||
+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11");
|
||||
+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12");
|
||||
+ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8");
|
||||
+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise");
|
||||
+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1");
|
||||
+ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil");
|
||||
+ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil");
|
||||
+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler");
|
||||
+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1");
|
||||
+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2");
|
||||
+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3");
|
||||
+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4");
|
||||
+ tryPreloadClass("org.slf4j.helpers.MessageFormatter");
|
||||
+ tryPreloadClass("org.slf4j.helpers.FormattingTuple");
|
||||
+ tryPreloadClass("org.slf4j.helpers.BasicMarker");
|
||||
+ tryPreloadClass("org.slf4j.helpers.Util");
|
||||
+ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent");
|
||||
+ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent");
|
||||
+ // Minecraft, seen during saving
|
||||
+ tryPreloadClass(LayerLightEventListener.DummyLightLayerEventListener.class.getName());
|
||||
+ tryPreloadClass(LayerLightEventListener.class.getName());
|
||||
+ tryPreloadClass(ExceptionCollector.class.getName());
|
||||
+ // Paper end
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Paper start
|
||||
+ private static void tryPreloadClass(String className) {
|
||||
+ tryPreloadClass(className, true);
|
||||
+ }
|
||||
+ private static void tryPreloadClass(String className, boolean printError) {
|
||||
+ try {
|
||||
+ Class.forName(className);
|
||||
+ } catch (ClassNotFoundException e) {
|
||||
+ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
+ // Paper end
|
||||
|
||||
private static List<String> asList(String... params) {
|
||||
return Arrays.asList(params);
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
||||
@@ -0,0 +0,0 @@ public class ServerShutdownThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
+ // Paper start - try to shutdown on main
|
||||
+ server.safeShutdown(false, false);
|
||||
+ for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) {
|
||||
+ Thread.sleep(100);
|
||||
+ }
|
||||
+ if (server.hasStopped()) {
|
||||
+ while (!server.hasFullyShutdown) Thread.sleep(1000);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Looks stalled, close async
|
||||
org.spigotmc.AsyncCatcher.enabled = false; // Spigot
|
||||
org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper
|
||||
+ server.forceTicks = true;
|
||||
this.server.close();
|
||||
+ while (!server.hasFullyShutdown) Thread.sleep(1000);
|
||||
+ } catch (InterruptedException e) {
|
||||
+ e.printStackTrace();
|
||||
+ // Paper end
|
||||
} finally {
|
||||
try {
|
||||
- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
|
||||
+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/spigotmc/RestartCommand.java
|
||||
+++ b/src/main/java/org/spigotmc/RestartCommand.java
|
||||
@@ -0,0 +0,0 @@ public class RestartCommand extends Command
|
||||
// Paper end
|
||||
|
||||
// Paper start - copied from above and modified to return if the hook registered
|
||||
- private static boolean addShutdownHook(String restartScript)
|
||||
+ public static boolean addShutdownHook(String restartScript)
|
||||
{
|
||||
String[] split = restartScript.split( " " );
|
||||
if ( split.length > 0 && new File( split[0] ).isFile() )
|
||||
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
@@ -0,0 +0,0 @@ import org.bukkit.Bukkit;
|
||||
public class WatchdogThread extends Thread
|
||||
{
|
||||
|
||||
+ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper
|
||||
private static WatchdogThread instance;
|
||||
private long timeoutTime;
|
||||
private boolean restart;
|
||||
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
|
||||
{
|
||||
if ( WatchdogThread.instance == null )
|
||||
{
|
||||
+ if (timeoutTime <= 0) timeoutTime = 300; // Paper
|
||||
WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
|
||||
WatchdogThread.instance.start();
|
||||
} else
|
||||
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
|
||||
// Paper start
|
||||
Logger log = Bukkit.getServer().getLogger();
|
||||
long currentTime = WatchdogThread.monotonicMillis();
|
||||
- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable
|
||||
+ MinecraftServer server = MinecraftServer.getServer();
|
||||
+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable
|
||||
{
|
||||
- boolean isLongTimeout = currentTime > lastTick + timeoutTime;
|
||||
+ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000);
|
||||
// Don't spam early warning dumps
|
||||
if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue;
|
||||
- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
|
||||
+ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
|
||||
lastEarlyWarning = currentTime;
|
||||
if (isLongTimeout) {
|
||||
// Paper end
|
||||
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
|
||||
log.log( Level.SEVERE, "------------------------------" );
|
||||
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
|
||||
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
|
||||
- WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
|
||||
+ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log );
|
||||
log.log( Level.SEVERE, "------------------------------" );
|
||||
//
|
||||
// Paper start - Only print full dump on long timeouts
|
||||
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
|
||||
|
||||
if ( isLongTimeout )
|
||||
{
|
||||
- if ( this.restart && !MinecraftServer.getServer().hasStopped() )
|
||||
+ if ( !server.hasStopped() )
|
||||
{
|
||||
- RestartCommand.restart();
|
||||
+ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
|
||||
+ AsyncCatcher.shuttingDown = true;
|
||||
+ server.forceTicks = true;
|
||||
+ if (restart) {
|
||||
+ RestartCommand.addShutdownHook( SpigotConfig.restartScript );
|
||||
+ }
|
||||
+ // try one last chance to safe shutdown on main incase it 'comes back'
|
||||
+ server.abnormalExit = true;
|
||||
+ server.safeShutdown(false, restart);
|
||||
+ try {
|
||||
+ Thread.sleep(1000);
|
||||
+ } catch (InterruptedException e) {
|
||||
+ e.printStackTrace();
|
||||
+ }
|
||||
+ if (!server.hasStopped()) {
|
||||
+ server.close();
|
||||
+ }
|
||||
}
|
||||
break;
|
||||
} // Paper end
|
||||
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/resources/log4j2.xml
|
||||
+++ b/src/main/resources/log4j2.xml
|
||||
@@ -0,0 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
-<Configuration status="WARN" packages="com.mojang.util">
|
||||
+<Configuration status="WARN" packages="com.mojang.util" shutdownHook="disable">
|
||||
<Appenders>
|
||||
<Queue name="ServerGuiConsole">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
|
||||
@@ -1,699 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Tue, 5 May 2020 21:23:34 -0700
|
||||
Subject: [PATCH] No-Tick view distance implementation
|
||||
|
||||
Implements world view distance getters/setters
|
||||
|
||||
Per-Player is absent due to difficulty of maintaining
|
||||
the diff required to make it happen.
|
||||
|
||||
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
@@ -0,0 +0,0 @@ public class TimingsExport extends Thread {
|
||||
pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
|
||||
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
||||
})),
|
||||
- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
|
||||
+ pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()),
|
||||
+ pair("notick-viewdistance", world.getChunkSource().chunkMap.getEffectiveNoTickViewDistance())
|
||||
));
|
||||
}));
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
|
||||
lightQueueSize = getInt("light-queue-size", lightQueueSize);
|
||||
}
|
||||
|
||||
+ public int noTickViewDistance;
|
||||
+ private void viewDistance() {
|
||||
+ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1);
|
||||
+ }
|
||||
+
|
||||
public boolean antiXray;
|
||||
public EngineMode engineMode;
|
||||
public int maxBlockHeight;
|
||||
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
||||
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
@@ -0,0 +0,0 @@ public final class MCUtil {
|
||||
});
|
||||
|
||||
worldData.addProperty("name", world.getWorld().getName());
|
||||
- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
|
||||
+ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance());
|
||||
+ worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.getRawNoTickViewDistance());
|
||||
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
|
||||
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
|
||||
worldData.addProperty("visible-chunk-count", visibleChunks.size());
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
|
||||
|
||||
boolean isUpdateQueued = false; // Paper
|
||||
private final ChunkMap chunkMap; // Paper
|
||||
+ // Paper start - no-tick view distance
|
||||
+ public final LevelChunk getSendingChunk() {
|
||||
+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
|
||||
+ // in Chunk's neighbour callback
|
||||
+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
|
||||
+ if (ret != null && ret.areNeighboursLoaded(1)) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+ // Paper end - no-tick view distance
|
||||
|
||||
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
|
||||
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
|
||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
|
||||
}
|
||||
|
||||
public void blockChanged(BlockPos pos) {
|
||||
- LevelChunk chunk = this.getTickingChunk();
|
||||
+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
|
||||
|
||||
if (chunk != null) {
|
||||
int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
|
||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
|
||||
}
|
||||
|
||||
public void sectionLightChanged(LightLayer lightType, int y) {
|
||||
- LevelChunk chunk = this.getTickingChunk();
|
||||
+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
|
||||
|
||||
if (chunk != null) {
|
||||
chunk.setUnsaved(true);
|
||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
|
||||
}
|
||||
|
||||
public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
|
||||
- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
|
||||
- entityplayer.connection.send(packet);
|
||||
- });
|
||||
+ // Paper start - per player view distance
|
||||
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
|
||||
+ // view distance map here.
|
||||
+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap;
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
|
||||
+ if (players == null) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (onlyOnWatchDistanceEdge) { // flag -> border only
|
||||
+ Object[] backingSet = players.getBackingSet();
|
||||
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
||||
+ Object temp = backingSet[i];
|
||||
+ if (!(temp instanceof ServerPlayer)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ ServerPlayer player = (ServerPlayer)temp;
|
||||
+
|
||||
+ int viewDistance = viewDistanceMap.getLastViewDistance(player);
|
||||
+ long lastPosition = viewDistanceMap.getLastCoordinate(player);
|
||||
+
|
||||
+ int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - this.pos.x);
|
||||
+ int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - this.pos.z);
|
||||
+
|
||||
+ if (Math.max(distX, distZ) == viewDistance) {
|
||||
+ player.connection.send(packet);
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
+ Object[] backingSet = players.getBackingSet();
|
||||
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
||||
+ Object temp = backingSet[i];
|
||||
+ if (!(temp instanceof ServerPlayer)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ ServerPlayer player = (ServerPlayer)temp;
|
||||
+ player.connection.send(packet);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return;
|
||||
+ // Paper end - per player view distance
|
||||
}
|
||||
|
||||
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
// Paper start - distance maps
|
||||
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
|
||||
+ // Paper start - no-tick view distance
|
||||
+ int noTickViewDistance;
|
||||
+ public final int getRawNoTickViewDistance() {
|
||||
+ return this.noTickViewDistance;
|
||||
+ }
|
||||
+ public final int getEffectiveNoTickViewDistance() {
|
||||
+ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance;
|
||||
+ }
|
||||
+ public final int getLoadViewDistance() {
|
||||
+ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance());
|
||||
+ }
|
||||
+
|
||||
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
|
||||
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
|
||||
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
|
||||
+ // Paper end - no-tick view distance
|
||||
|
||||
void addPlayerToDistanceMaps(ServerPlayer player) {
|
||||
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
||||
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
||||
// Note: players need to be explicitly added to distance maps before they can be updated
|
||||
+ // Paper start - no-tick view distance
|
||||
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
|
||||
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
|
||||
+
|
||||
+ if (!this.skipPlayer(player)) {
|
||||
+ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance);
|
||||
+ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
|
||||
+ }
|
||||
+
|
||||
+ player.needsChunkCenterUpdate = true;
|
||||
+ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
|
||||
+ player.needsChunkCenterUpdate = false;
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
|
||||
void removePlayerFromDistanceMaps(ServerPlayer player) {
|
||||
|
||||
+ // Paper start - no-tick view distance
|
||||
+ this.playerViewDistanceBroadcastMap.remove(player);
|
||||
+ this.playerViewDistanceTickMap.remove(player);
|
||||
+ this.playerViewDistanceNoTickMap.remove(player);
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
|
||||
void updateMaps(ServerPlayer player) {
|
||||
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
||||
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
||||
// Note: players need to be explicitly added to distance maps before they can be updated
|
||||
+ // Paper start - no-tick view distance
|
||||
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
|
||||
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
|
||||
+
|
||||
+ if (!this.skipPlayer(player)) {
|
||||
+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance);
|
||||
+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
|
||||
+ }
|
||||
+
|
||||
+ player.needsChunkCenterUpdate = true;
|
||||
+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
|
||||
+ player.needsChunkCenterUpdate = false;
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
// Paper end
|
||||
// Paper start
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
||||
this.regionManagers.add(this.dataRegionManager);
|
||||
// Paper end
|
||||
+ // Paper start - no-tick view distance
|
||||
+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
|
||||
+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
||||
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
||||
+ if (newState.size() != 1) {
|
||||
+ return;
|
||||
+ }
|
||||
+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
|
||||
+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
||||
+ ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
||||
+ },
|
||||
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
||||
+ if (newState != null) {
|
||||
+ return;
|
||||
+ }
|
||||
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
||||
+ ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
||||
+ });
|
||||
+ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
|
||||
+ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
||||
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
||||
+ if (player.needsChunkCenterUpdate) {
|
||||
+ player.needsChunkCenterUpdate = false;
|
||||
+ player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ));
|
||||
+ }
|
||||
+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded
|
||||
+ },
|
||||
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
||||
+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded
|
||||
+ });
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
|
||||
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
completablefuture1.thenAcceptAsync((either) -> {
|
||||
either.ifLeft((chunk) -> {
|
||||
this.tickingGenerated.getAndIncrement();
|
||||
- Packet<?>[] apacket = new Packet[2];
|
||||
-
|
||||
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
||||
- this.playerLoadedChunk(entityplayer, apacket, chunk);
|
||||
- });
|
||||
+ // Paper - no-tick view distance - moved to Chunk neighbour update
|
||||
});
|
||||
}, (runnable) -> {
|
||||
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
|
||||
+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again)
|
||||
});
|
||||
return completablefuture1;
|
||||
}
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void setViewDistance(int watchDistance) {
|
||||
- int j = Mth.clamp(watchDistance + 1, 3, 33);
|
||||
+ int j = Mth.clamp(watchDistance + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
|
||||
|
||||
if (j != this.viewDistance) {
|
||||
int k = this.viewDistance;
|
||||
|
||||
this.viewDistance = j;
|
||||
- this.distanceManager.updatePlayerTickets(this.viewDistance);
|
||||
- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator();
|
||||
+ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); // Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending
|
||||
+ }
|
||||
|
||||
- while (objectiterator.hasNext()) {
|
||||
- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
|
||||
- ChunkPos chunkcoordintpair = playerchunk.getPos();
|
||||
- Packet<?>[] apacket = new Packet[2];
|
||||
+ }
|
||||
|
||||
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
||||
- int l = ChunkMap.checkerboardDistance(chunkcoordintpair, entityplayer, true);
|
||||
- boolean flag = l <= k;
|
||||
- boolean flag1 = l <= this.viewDistance;
|
||||
+ // Paper start - no-tick view distance
|
||||
+ public final void setNoTickViewDistance(int viewDistance) {
|
||||
+ viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32);
|
||||
|
||||
- this.updateChunkTracking(entityplayer, chunkcoordintpair, apacket, flag, flag1);
|
||||
- });
|
||||
+ this.noTickViewDistance = viewDistance;
|
||||
+ int loadViewDistance = this.getLoadViewDistance();
|
||||
+ this.distanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
|
||||
+
|
||||
+ if (this.level != null && this.level.players != null) { // this can be called from constructor, where these aren't set
|
||||
+ for (ServerPlayer player : this.level.players) {
|
||||
+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
|
||||
+ if (connection != null) {
|
||||
+ // moved in from PlayerList
|
||||
+ connection.send(new net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket(loadViewDistance));
|
||||
+ }
|
||||
+ this.updateMaps(player);
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
|
||||
|
||||
if (playerchunk != null) {
|
||||
- LevelChunk chunk = playerchunk.getTickingChunk();
|
||||
+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance
|
||||
|
||||
if (chunk != null) {
|
||||
this.playerLoadedChunk(player, packets, chunk);
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
this.removePlayerFromDistanceMaps(player); // Paper - distance maps
|
||||
}
|
||||
|
||||
- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
|
||||
- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) {
|
||||
- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
|
||||
-
|
||||
- this.updateChunkTracking(player, chunkcoordintpair, new Packet[2], !added, added);
|
||||
- }
|
||||
- }
|
||||
+ // Paper - broadcast view distance map handles this (see remove/add calls above)
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
SectionPos sectionposition = SectionPos.of((Entity) player);
|
||||
|
||||
player.setLastSectionPos(sectionposition);
|
||||
- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
|
||||
+ // player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - distance map handles this now
|
||||
return sectionposition;
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
int k1;
|
||||
int l1;
|
||||
|
||||
+ /* // Paper start - replaced by distance map
|
||||
if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
|
||||
k1 = Math.min(i, i1) - this.viewDistance;
|
||||
l1 = Math.min(j, j1) - this.viewDistance;
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
+ */ // Paper end - replaced by distance map
|
||||
|
||||
this.updateMaps(player); // Paper - distance maps
|
||||
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
@Override
|
||||
public Stream<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
|
||||
- return this.playerMap.getPlayers(chunkPos.toLong()).filter((entityplayer) -> {
|
||||
- int i = ChunkMap.checkerboardDistance(chunkPos, entityplayer, true);
|
||||
-
|
||||
- return i > this.viewDistance ? false : !onlyOnWatchDistanceEdge || i == this.viewDistance;
|
||||
- });
|
||||
+ // Paper start - per player view distance
|
||||
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
|
||||
+ // view distance map here.
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkPos);
|
||||
+
|
||||
+ if (inRange == null) {
|
||||
+ return Stream.empty();
|
||||
+ }
|
||||
+ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it
|
||||
+ List<ServerPlayer> players = new java.util.ArrayList<>();
|
||||
+ Object[] backingSet = inRange.getBackingSet();
|
||||
+
|
||||
+ if (onlyOnWatchDistanceEdge) { // flag -> border only
|
||||
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
||||
+ Object temp = backingSet[i];
|
||||
+ if (!(temp instanceof ServerPlayer)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ ServerPlayer player = (ServerPlayer)temp;
|
||||
+ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
|
||||
+ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
|
||||
+
|
||||
+ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x);
|
||||
+ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z);
|
||||
+ if (Math.max(distX, distZ) == viewDistance) {
|
||||
+ players.add(player);
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
||||
+ Object temp = backingSet[i];
|
||||
+ if (!(temp instanceof ServerPlayer)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ ServerPlayer player = (ServerPlayer)temp;
|
||||
+ players.add(player);
|
||||
+ }
|
||||
+ }
|
||||
+ return players.stream();
|
||||
+ // Paper end - per player view distance
|
||||
}
|
||||
|
||||
public void addEntity(Entity entity) {
|
||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private static int getLightMask(final LevelChunk chunk) {
|
||||
+ final net.minecraft.world.level.chunk.LevelChunkSection[] chunkSections = chunk.getSections();
|
||||
+ int mask = 0;
|
||||
+
|
||||
+ for (int i = 0; i < chunkSections.length; ++i) {
|
||||
+ /*
|
||||
+
|
||||
+
|
||||
+Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section.
|
||||
+Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d.
|
||||
+
|
||||
+ */
|
||||
+ mask |= (net.minecraft.world.level.chunk.LevelChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i;
|
||||
+ }
|
||||
+
|
||||
+ return mask;
|
||||
+ }
|
||||
+
|
||||
+ private static int getCeilingLightMask(final LevelChunk chunk) {
|
||||
+ int mask = getLightMask(chunk);
|
||||
+
|
||||
+ /*
|
||||
+ It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below.
|
||||
+ We then invert this, so we'd have 110000 and compare that to the "main" chunk.
|
||||
+ This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights.
|
||||
+
|
||||
+ so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow.
|
||||
+ @TODO: Implement Leafs suggestion
|
||||
+ either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then
|
||||
+ */
|
||||
+ mask |= mask >> 1;
|
||||
+ mask |= mask >> 2;
|
||||
+ mask |= mask >> 4;
|
||||
+ mask |= mask >> 8;
|
||||
+ mask |= mask >> 16;
|
||||
+
|
||||
+ return mask;
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) {
|
||||
if (packets[0] == null) {
|
||||
packets[0] = new ClientboundLevelChunkPacket(chunk, chunk.level.chunkPacketBlockController.shouldModify(player, chunk)); // Paper - Ani-Xray - Bypass
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
|
||||
return s;
|
||||
}
|
||||
|
||||
- protected void updatePlayerTickets(int viewDistance) {
|
||||
- this.playerTicketManager.updateViewDistance(viewDistance);
|
||||
+ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change
|
||||
+ this.playerTicketManager.updateViewDistance(i);
|
||||
}
|
||||
|
||||
public int getNaturalSpawnChunkCount() {
|
||||
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
|
||||
|
||||
private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) {
|
||||
if (oldWithinViewDistance != withinViewDistance) {
|
||||
- Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, DistanceManager.PLAYER_TICKET_LEVEL, new ChunkPos(pos));
|
||||
+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance
|
||||
|
||||
if (withinViewDistance) {
|
||||
DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
|
||||
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
|
||||
|
||||
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
|
||||
+ boolean needsChunkCenterUpdate; // Paper - no-tick view distance
|
||||
|
||||
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
|
||||
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
|
||||
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
|
||||
|
||||
// Spigot - view distance
|
||||
- playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat()));
|
||||
+ playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance
|
||||
player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
|
||||
playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
|
||||
playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
// CraftBukkit start
|
||||
LevelData worlddata = worldserver1.getLevelData();
|
||||
entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag));
|
||||
- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
|
||||
+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance
|
||||
entityplayer1.setLevel(worldserver1);
|
||||
entityplayer1.unsetRemoved();
|
||||
entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
|
||||
public void setViewDistance(int viewDistance) {
|
||||
this.viewDistance = viewDistance;
|
||||
- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
|
||||
+ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance
|
||||
Iterator iterator = this.server.getAllLevels().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/Level.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
||||
this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
|
||||
}
|
||||
|
||||
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
|
||||
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below
|
||||
this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
|
||||
+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
|
||||
+ // if copied from above
|
||||
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) {
|
||||
+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
|
||||
+ // Paper end - per player view distance
|
||||
}
|
||||
|
||||
if ((i & 1) != 0) {
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
@@ -0,0 +0,0 @@ import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
+import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
+import net.minecraft.server.level.ChunkMap;
|
||||
+import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
|
||||
}
|
||||
|
||||
protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
|
||||
+ // Paper start - no-tick view distance
|
||||
+ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
|
||||
+ ChunkMap chunkMap = chunkProviderServer.chunkMap;
|
||||
+ // this code handles the addition of ticking tickets - the distance map handles the removal
|
||||
+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
|
||||
+ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) {
|
||||
+ // now we're ready for entity ticking
|
||||
+ chunkProviderServer.mainThreadProcessor.execute(() -> {
|
||||
+ // double check that this condition still holds.
|
||||
+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) {
|
||||
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
|
||||
+ // this code handles the chunk sending
|
||||
+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
|
||||
+ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) {
|
||||
+ // now we're ready to send
|
||||
+ chunkMap.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(chunkMap.getUpdatingChunkIfPresent(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap
|
||||
+ // double check that this condition still holds.
|
||||
+ if (!LevelChunk.this.areNeighboursLoaded(1)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(LevelChunk.this.coordinateKey);
|
||||
+ if (inRange == null) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ // broadcast
|
||||
+ Object[] backingSet = inRange.getBackingSet();
|
||||
+ Packet[] chunkPackets = new Packet[2];
|
||||
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
|
||||
+ Object temp = backingSet[index];
|
||||
+ if (!(temp instanceof net.minecraft.server.level.ServerPlayer)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)temp;
|
||||
+ chunkMap.playerLoadedChunk(player, chunkPackets, LevelChunk.this);
|
||||
+ }
|
||||
+ })));
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - no-tick view distance
|
||||
}
|
||||
|
||||
public final boolean isAnyNeighborsLoaded() {
|
||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
|
||||
BlockState iblockdata = this.getBlockState(blockposition);
|
||||
BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, (LevelAccessor) this.level, blockposition);
|
||||
|
||||
- this.level.setBlock(blockposition, iblockdata1, 20);
|
||||
+ this.level.setBlock(blockposition, iblockdata1, 20 | 2); // Paper - We send chunks before they're ticking ready, so we need to notify here
|
||||
}
|
||||
|
||||
this.postProcessing[i].clear();
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||
// Spigot start
|
||||
@Override
|
||||
public int getViewDistance() {
|
||||
- return world.spigotConfig.viewDistance;
|
||||
+ return getHandle().getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance
|
||||
}
|
||||
// Spigot end
|
||||
|
||||
+ // Paper start - per player view distance
|
||||
+ @Override
|
||||
+ public void setViewDistance(int viewDistance) {
|
||||
+ if (viewDistance < 2 || viewDistance > 32) {
|
||||
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
||||
+ }
|
||||
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
|
||||
+ if (viewDistance != chunkMap.getEffectiveViewDistance()) {
|
||||
+ chunkMap.setViewDistance(viewDistance);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getNoTickViewDistance() {
|
||||
+ return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setNoTickViewDistance(int viewDistance) {
|
||||
+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
|
||||
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
||||
+ }
|
||||
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
|
||||
+ if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
|
||||
+ chunkMap.setNoTickViewDistance(viewDistance);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - per player view distance
|
||||
+
|
||||
// Spigot start
|
||||
private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
|
||||
{
|
||||
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/spigotmc/ActivationRange.java
|
||||
+++ b/src/main/java/org/spigotmc/ActivationRange.java
|
||||
@@ -0,0 +0,0 @@ public class ActivationRange
|
||||
maxRange = Math.max( maxRange, waterActivationRange );
|
||||
maxRange = Math.max( maxRange, villagerActivationRange );
|
||||
// Paper end
|
||||
- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
|
||||
+ maxRange = Math.min( ( ((net.minecraft.server.level.ServerLevel)world).getChunkSource().chunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance
|
||||
|
||||
for ( Player player : world.players() )
|
||||
{
|
||||
@@ -1,53 +0,0 @@
|
||||
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/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
+++ b/src/main/java/net/minecraft/util/SortedArraySet.java
|
||||
@@ -0,0 +0,0 @@ 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);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,121 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Thu, 2 Apr 2020 02:37:57 -0400
|
||||
Subject: [PATCH] Optimize Collision to not load chunks
|
||||
|
||||
The collision code takes an AABB and generates a cuboid of checks rather
|
||||
than a cylinder, so at high velocity this can generate a lot of chunk checks.
|
||||
|
||||
Treat an unloaded chunk as a collision for entities, and also for players if
|
||||
the "prevent moving into unloaded chunks" setting is enabled.
|
||||
|
||||
If that serting is not enabled, collisions will be ignored for players, since
|
||||
movement will load only the chunk the player enters anyways and avoids loading
|
||||
massive amounts of surrounding chunks due to large AABB lookups.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
||||
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
||||
entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
|
||||
// CraftBukkit end
|
||||
|
||||
+ worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper
|
||||
while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
|
||||
entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
|
||||
// Paper end
|
||||
|
||||
public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
|
||||
+ public boolean collisionLoadChunks = false; // Paper
|
||||
private CraftEntity bukkitEntity;
|
||||
|
||||
public net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper
|
||||
diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java
|
||||
@@ -0,0 +0,0 @@ public interface CollisionGetter extends BlockGetter {
|
||||
}
|
||||
|
||||
default boolean noCollision(@Nullable Entity entity, AABB box, Predicate<Entity> filter) {
|
||||
+ try { if (entity != null) entity.collisionLoadChunks = true; // Paper
|
||||
return this.getCollisions(entity, box, filter).allMatch(VoxelShape::isEmpty);
|
||||
+ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
|
||||
}
|
||||
|
||||
Stream<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box, Predicate<Entity> predicate);
|
||||
diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
|
||||
@@ -0,0 +0,0 @@ public class CollisionSpliterator extends AbstractSpliterator<VoxelShape> {
|
||||
boolean collisionCheck(Consumer<? super VoxelShape> action) {
|
||||
while(true) {
|
||||
if (this.cursor.advance()) {
|
||||
- int i = this.cursor.nextX();
|
||||
- int j = this.cursor.nextY();
|
||||
- int k = this.cursor.nextZ();
|
||||
+ int i = this.cursor.nextX(); final int x = i;
|
||||
+ int j = this.cursor.nextY(); final int y = j;
|
||||
+ int k = this.cursor.nextZ(); final int z = k;
|
||||
int l = this.cursor.getNextType();
|
||||
if (l == 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
- BlockGetter blockGetter = this.getChunk(i, k);
|
||||
- if (blockGetter == null) {
|
||||
+ // Paper start - ensure we don't load chunks
|
||||
+ boolean far = this.source != null && net.minecraft.server.MCUtil.distanceSq(this.source.getX(), y, this.source.getZ(), x, y, z) > 14;
|
||||
+ this.pos.set(x, y, z);
|
||||
+
|
||||
+ BlockState blockState;
|
||||
+ if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) {
|
||||
+ BlockGetter blockGetter = this.getChunk(x, z);
|
||||
+ if (blockGetter == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ blockState = blockGetter.getBlockState(this.pos);
|
||||
+ } else if ((!far && this.source instanceof net.minecraft.server.level.ServerPlayer) || (this.source != null && this.source.collisionLoadChunks)) {
|
||||
+ blockState = this.collisionGetter.getBlockState(this.pos);
|
||||
+ } else {
|
||||
+ blockState = this.collisionGetter.getTypeIfLoaded(this.pos);
|
||||
+ }
|
||||
+
|
||||
+ if (blockState == null) {
|
||||
+ if (!(this.source instanceof net.minecraft.server.level.ServerPlayer) || this.source.level.paperConfig.preventMovingIntoUnloadedChunks) {
|
||||
+ VoxelShape voxelshape3 = Shapes.create(far ? this.source.getBoundingBox() : new AABB(new BlockPos(x, y, z)));
|
||||
+ action.accept(voxelshape3);
|
||||
+ return true;
|
||||
+ }
|
||||
continue;
|
||||
}
|
||||
+ // Paper - moved up
|
||||
+ // Paper end
|
||||
|
||||
- this.pos.set(i, j, k);
|
||||
- BlockState blockState = blockGetter.getBlockState(this.pos);
|
||||
if (!this.predicate.test(blockState, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) {
|
||||
continue;
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
|
||||
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
|
||||
@@ -0,0 +0,0 @@ public final class Shapes {
|
||||
|
||||
if (s < 3) {
|
||||
mutableBlockPos.set(axisCycle, q, r, p);
|
||||
- BlockState blockState = world.getBlockState(mutableBlockPos);
|
||||
+ BlockState blockState = world.getTypeIfLoaded(mutableBlockPos); // Paper
|
||||
+ if (blockState == null) return 0.0D; // Paper
|
||||
if ((s != 1 || blockState.hasLargeCollisionShape()) && (s != 2 || blockState.is(Blocks.MOVING_PISTON))) {
|
||||
initial = blockState.getCollisionShape(world, mutableBlockPos, context).collide(axis3, box.move((double)(-mutableBlockPos.getX()), (double)(-mutableBlockPos.getY()), (double)(-mutableBlockPos.getZ())), initial);
|
||||
if (Math.abs(initial) < 1.0E-7D) {
|
||||
@@ -1,43 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Thu, 3 Mar 2016 02:02:07 -0600
|
||||
Subject: [PATCH] Optimize Pathfinding
|
||||
|
||||
Prevents pathfinding from spamming failures for things such as
|
||||
arrow attacks.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
|
||||
@@ -0,0 +0,0 @@ public abstract class PathNavigation {
|
||||
return this.moveTo(this.createPath(x, y, z, 1), speed);
|
||||
}
|
||||
|
||||
+ // Paper start - optimise pathfinding
|
||||
+ private int lastFailure = 0;
|
||||
+ private int pathfindFailures = 0;
|
||||
+ // Paper end
|
||||
+
|
||||
public boolean moveTo(Entity entity, double speed) {
|
||||
+ // Paper start - Pathfinding optimizations
|
||||
+ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ // Paper end
|
||||
Path path = this.createPath(entity, 1);
|
||||
- return path != null && this.moveTo(path, speed);
|
||||
+ // Paper start - Pathfinding optimizations
|
||||
+ if (path != null && this.moveTo(path, speed)) {
|
||||
+ this.lastFailure = 0;
|
||||
+ this.pathfindFailures = 0;
|
||||
+ return true;
|
||||
+ } else {
|
||||
+ this.pathfindFailures++;
|
||||
+ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick;
|
||||
+ return false;
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
public boolean moveTo(@Nullable Path path, double speed) {
|
||||
@@ -1,50 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shane Freeder <theboyetronic@gmail.com>
|
||||
Date: Mon, 13 Apr 2020 07:31:44 +0100
|
||||
Subject: [PATCH] Prevent opening inventories when frozen
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
|
||||
containerUpdateDelay = level.paperConfig.containerUpdateTickRate;
|
||||
}
|
||||
// Paper end
|
||||
- if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) {
|
||||
+ if (!this.level.isClientSide && this.containerMenu != this.inventoryMenu && (isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen
|
||||
this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper
|
||||
this.containerMenu = this.inventoryMenu;
|
||||
}
|
||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
|
||||
} else {
|
||||
// CraftBukkit start
|
||||
this.containerMenu = container;
|
||||
- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle()));
|
||||
+ if (!isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper
|
||||
// CraftBukkit end
|
||||
this.initMenu(container);
|
||||
return OptionalInt.of(this.containerCounter);
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
|
||||
@@ -0,0 +0,0 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
|
||||
if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(container.getBukkitView().getTitle()); // Paper
|
||||
|
||||
//player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper // Paper - comment
|
||||
- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper
|
||||
+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper
|
||||
player.containerMenu = container;
|
||||
player.initMenu(container);
|
||||
}
|
||||
@@ -0,0 +0,0 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
|
||||
net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper
|
||||
if (adventure$title == null) adventure$title = io.papermc.paper.adventure.PaperAdventure.LEGACY_SECTION_UXRC.deserialize(inventory.getTitle()); // Paper
|
||||
//player.playerConnection.sendPacket(new PacketPlayOutOpenWindow(container.windowId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment
|
||||
- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper
|
||||
+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper
|
||||
player.containerMenu = container;
|
||||
player.initMenu(container);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Mon, 6 Apr 2020 18:35:09 -0700
|
||||
Subject: [PATCH] Reduce Either Optional allocation
|
||||
|
||||
In order to get chunk values, we shouldn't need to create
|
||||
an optional each time.
|
||||
|
||||
diff --git a/src/main/java/com/mojang/datafixers/util/Either.java b/src/main/java/com/mojang/datafixers/util/Either.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/com/mojang/datafixers/util/Either.java
|
||||
+++ b/src/main/java/com/mojang/datafixers/util/Either.java
|
||||
@@ -0,0 +0,0 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
|
||||
}
|
||||
|
||||
private static final class Left<L, R> extends Either<L, R> {
|
||||
- private final L value;
|
||||
+ private final L value; private Optional<L> valueOptional; // Paper - reduce the optional allocation...
|
||||
|
||||
public Left(final L value) {
|
||||
this.value = value;
|
||||
@@ -0,0 +0,0 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
|
||||
|
||||
@Override
|
||||
public Optional<L> left() {
|
||||
- return Optional.of(value);
|
||||
+ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation...
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +0,0 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
|
||||
}
|
||||
|
||||
private static final class Right<L, R> extends Either<L, R> {
|
||||
- private final R value;
|
||||
+ private final R value; private Optional<R> valueOptional; // Paper - reduce the optional allocation...
|
||||
|
||||
public Right(final R value) {
|
||||
this.value = value;
|
||||
@@ -0,0 +0,0 @@ public abstract class Either<L, R> implements App<Either.Mu<R>, L> {
|
||||
|
||||
@Override
|
||||
public Optional<R> right() {
|
||||
- return Optional.of(value);
|
||||
+ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - reduce the optional allocation...
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,50 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Mon, 6 Apr 2020 17:39:25 -0700
|
||||
Subject: [PATCH] Reduce memory footprint of NBTTagCompound
|
||||
|
||||
Fastutil maps are going to have a lower memory footprint - which
|
||||
is important because we clone chunk data after reading it for safety.
|
||||
So, reduce the impact of the clone on GC.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/nbt/CompoundTag.java
|
||||
+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java
|
||||
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
|
||||
if (i > 512) {
|
||||
throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512");
|
||||
} else {
|
||||
- Map<String, Tag> map = Maps.newHashMap();
|
||||
+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<String, Tag> map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - reduce memory footprint of NBTTagCompound
|
||||
|
||||
byte b;
|
||||
while((b = CompoundTag.readNamedTagType(dataInput, nbtAccounter)) != 0) {
|
||||
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
|
||||
}
|
||||
|
||||
public CompoundTag() {
|
||||
- this(Maps.newHashMap());
|
||||
+ this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - reduce memory footprint of NBTTagCompound
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
|
||||
|
||||
@Override
|
||||
public CompoundTag copy() {
|
||||
- Map<String, Tag> map = Maps.newHashMap(Maps.transformValues(this.tags, Tag::copy));
|
||||
- return new CompoundTag(map);
|
||||
+ // Paper start - reduce memory footprint of NBTTagCompound
|
||||
+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<String, Tag> ret = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.tags.size(), 0.8f);
|
||||
+ java.util.Iterator<java.util.Map.Entry<String, Tag>> iterator = (this.tags instanceof it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) ? ((it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap)this.tags).object2ObjectEntrySet().fastIterator() : this.tags.entrySet().iterator();
|
||||
+ while (iterator.hasNext()) {
|
||||
+ Map.Entry<String, Tag> entry = iterator.next();
|
||||
+ ret.put(entry.getKey(), entry.getValue().copy());
|
||||
+ }
|
||||
+
|
||||
+ return new CompoundTag(ret);
|
||||
+ // Paper end - reduce memory footprint of NBTTagCompound
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,223 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Mon, 6 Apr 2020 17:53:29 -0700
|
||||
Subject: [PATCH] Remove streams from Mob AI System
|
||||
|
||||
The streams hurt performance and allocate tons of garbage, so
|
||||
replace them with the standard iterator.
|
||||
|
||||
Also optimise the stream.anyMatch statement to move to a bitset
|
||||
where we can replace the call with a single bitwise operation.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
|
||||
@@ -0,0 +0,0 @@ package net.minecraft.world.entity.ai.goal;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public abstract class Goal {
|
||||
- private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class);
|
||||
+ private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be.
|
||||
+ private final com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
|
||||
|
||||
public abstract boolean canUse();
|
||||
|
||||
@@ -0,0 +0,0 @@ public abstract class Goal {
|
||||
}
|
||||
|
||||
public void setFlags(EnumSet<Goal.Flag> controls) {
|
||||
- this.flags.clear();
|
||||
- this.flags.addAll(controls);
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ this.goalTypes.clear();
|
||||
+ this.goalTypes.addAllUnchecked(controls);
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +0,0 @@ public abstract class Goal {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
- public EnumSet<Goal.Flag> getFlags() {
|
||||
- return this.flags;
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() {
|
||||
+ return this.goalTypes;
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
public static enum Flag {
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
|
||||
@@ -0,0 +0,0 @@ public class GoalSelector {
|
||||
private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
|
||||
public final Set<WrappedGoal> availableGoals = Sets.newLinkedHashSet();
|
||||
private final Supplier<ProfilerFiller> profiler;
|
||||
- private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
|
||||
+ private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be.
|
||||
+ private final com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
|
||||
private int tickCount;
|
||||
private int newGoalRate = 3;
|
||||
private int curRate;
|
||||
+ private static final Goal.Flag[] PATHFINDER_GOAL_TYPES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
|
||||
|
||||
public GoalSelector(Supplier<ProfilerFiller> profiler) {
|
||||
this.profiler = profiler;
|
||||
@@ -0,0 +0,0 @@ public class GoalSelector {
|
||||
}
|
||||
// Paper end
|
||||
public void removeGoal(Goal goal) {
|
||||
- this.availableGoals.stream().filter((wrappedGoal) -> {
|
||||
- return wrappedGoal.getGoal() == goal;
|
||||
- }).filter(WrappedGoal::isRunning).forEach(WrappedGoal::stop);
|
||||
- this.availableGoals.removeIf((wrappedGoal) -> {
|
||||
- return wrappedGoal.getGoal() == goal;
|
||||
- });
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ for (java.util.Iterator<WrappedGoal> iterator = this.availableGoals.iterator(); iterator.hasNext();) {
|
||||
+ WrappedGoal goalWrapped = iterator.next();
|
||||
+ if (goalWrapped.getGoal() != goal) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (goalWrapped.isRunning()) {
|
||||
+ goalWrapped.stop();
|
||||
+ }
|
||||
+ iterator.remove();
|
||||
+ }
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
ProfilerFiller profilerFiller = this.profiler.get();
|
||||
profilerFiller.push("goalCleanup");
|
||||
- this.getRunningGoals().filter((wrappedGoal) -> {
|
||||
- return !wrappedGoal.isRunning() || wrappedGoal.getFlags().stream().anyMatch(this.disabledFlags::contains) || !wrappedGoal.canContinueToUse();
|
||||
- }).forEach(Goal::stop);
|
||||
- this.lockedFlags.forEach((flag, wrappedGoal) -> {
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ for (java.util.Iterator<WrappedGoal> iterator = this.availableGoals.iterator(); iterator.hasNext();) {
|
||||
+ WrappedGoal wrappedGoal = iterator.next();
|
||||
if (!wrappedGoal.isRunning()) {
|
||||
- this.lockedFlags.remove(flag);
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (!this.goalTypes.hasCommonElements(wrappedGoal.getFlags()) && wrappedGoal.canContinueToUse()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ wrappedGoal.stop();
|
||||
+ }
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
+ this.lockedFlags.forEach((pathfindergoal_type, pathfindergoalwrapped) -> {
|
||||
+ if (!pathfindergoalwrapped.isRunning()) {
|
||||
+ this.lockedFlags.remove(pathfindergoal_type);
|
||||
}
|
||||
|
||||
});
|
||||
profilerFiller.pop();
|
||||
profilerFiller.push("goalUpdate");
|
||||
- this.availableGoals.stream().filter((wrappedGoal) -> {
|
||||
- return !wrappedGoal.isRunning();
|
||||
- }).filter((wrappedGoal) -> {
|
||||
- return wrappedGoal.getFlags().stream().noneMatch(this.disabledFlags::contains);
|
||||
- }).filter((wrappedGoal) -> {
|
||||
- return wrappedGoal.getFlags().stream().allMatch((flag) -> {
|
||||
- return this.lockedFlags.getOrDefault(flag, NO_GOAL).canBeReplacedBy(wrappedGoal);
|
||||
- });
|
||||
- }).filter(WrappedGoal::canUse).forEach((wrappedGoal) -> {
|
||||
- wrappedGoal.getFlags().forEach((flag) -> {
|
||||
- WrappedGoal wrappedGoal2 = this.lockedFlags.getOrDefault(flag, NO_GOAL);
|
||||
- wrappedGoal2.stop();
|
||||
- this.lockedFlags.put(flag, wrappedGoal);
|
||||
- });
|
||||
+
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ goal_update_loop: for (java.util.Iterator<WrappedGoal> iterator = this.availableGoals.iterator(); iterator.hasNext();) {
|
||||
+ WrappedGoal wrappedGoal = iterator.next();
|
||||
+ if (wrappedGoal.isRunning()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> wrappedGoalSet = wrappedGoal.getFlags();
|
||||
+
|
||||
+ if (this.goalTypes.hasCommonElements(wrappedGoalSet)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ long iterator1 = wrappedGoalSet.getBackingSet();
|
||||
+ int wrappedGoalSize = wrappedGoalSet.size();
|
||||
+ for (int i = 0; i < wrappedGoalSize; ++i) {
|
||||
+ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)];
|
||||
+ iterator1 ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(iterator1);
|
||||
+ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL);
|
||||
+ if (!wrapped.canBeReplacedBy(wrappedGoal)) {
|
||||
+ continue goal_update_loop;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!wrappedGoal.canUse()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ iterator1 = wrappedGoalSet.getBackingSet();
|
||||
+ wrappedGoalSize = wrappedGoalSet.size();
|
||||
+ for (int i = 0; i < wrappedGoalSize; ++i) {
|
||||
+ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)];
|
||||
+ iterator1 ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(iterator1);
|
||||
+ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL);
|
||||
+
|
||||
+ wrapped.stop();
|
||||
+ this.lockedFlags.put(type, wrappedGoal);
|
||||
+ }
|
||||
wrappedGoal.start();
|
||||
- });
|
||||
+ }
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
profilerFiller.pop();
|
||||
profilerFiller.push("goalTick");
|
||||
- this.getRunningGoals().forEach(WrappedGoal::tick);
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ for (java.util.Iterator<WrappedGoal> iterator = this.availableGoals.iterator(); iterator.hasNext();) {
|
||||
+ WrappedGoal wrappedGoal = iterator.next();
|
||||
+ if (wrappedGoal.isRunning()) {
|
||||
+ wrappedGoal.tick();
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
profilerFiller.pop();
|
||||
}
|
||||
|
||||
@@ -0,0 +0,0 @@ public class GoalSelector {
|
||||
}
|
||||
|
||||
public void disableControlFlag(Goal.Flag control) {
|
||||
- this.disabledFlags.add(control);
|
||||
+ this.goalTypes.addUnchecked(control); // Paper - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
public void enableControlFlag(Goal.Flag control) {
|
||||
- this.disabledFlags.remove(control);
|
||||
+ this.goalTypes.removeUnchecked(control); // Paper - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
public void setControlFlag(Goal.Flag control, boolean enabled) {
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
|
||||
@@ -0,0 +0,0 @@ public class WrappedGoal extends Goal {
|
||||
this.goal.setFlags(controls);
|
||||
}
|
||||
|
||||
- @Override
|
||||
- public EnumSet<Goal.Flag> getFlags() {
|
||||
+ // Paper start - remove streams from pathfindergoalselector
|
||||
+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() {
|
||||
return this.goal.getFlags();
|
||||
+ // Paper end - remove streams from pathfindergoalselector
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
@@ -1,46 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
||||
Date: Mon, 6 Apr 2020 18:10:43 -0700
|
||||
Subject: [PATCH] Remove streams from PairedQueue
|
||||
|
||||
We shouldn't be doing stream calls just to see if the queue is
|
||||
empty. This creates loads of garbage thanks to how often it's called.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/util/thread/StrictQueue.java b/src/main/java/net/minecraft/util/thread/StrictQueue.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/util/thread/StrictQueue.java
|
||||
+++ b/src/main/java/net/minecraft/util/thread/StrictQueue.java
|
||||
@@ -0,0 +0,0 @@ public interface StrictQueue<T, F> {
|
||||
private final List<Queue<Runnable>> queueList;
|
||||
|
||||
public FixedPriorityQueue(int priorityCount) {
|
||||
- this.queueList = IntStream.range(0, priorityCount).mapToObj((i) -> {
|
||||
- return Queues.newConcurrentLinkedQueue();
|
||||
- }).collect(Collectors.toList());
|
||||
+ // Paper start - remove streams
|
||||
+ this.queueList = new java.util.ArrayList<>(priorityCount); // queues
|
||||
+ for (int j = 0; j < priorityCount; ++j) {
|
||||
+ this.queueList.add(Queues.newConcurrentLinkedQueue());
|
||||
+ }
|
||||
+ // Paper end - remove streams
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -0,0 +0,0 @@ public interface StrictQueue<T, F> {
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
- return this.queueList.stream().allMatch(Collection::isEmpty);
|
||||
+ // Paper start - remove streams
|
||||
+ // why are we doing streams every time we might want to execute a task?
|
||||
+ for (int i = 0, len = this.queueList.size(); i < len; ++i) {
|
||||
+ Queue<Runnable> queue = this.queueList.get(i);
|
||||
+ if (!queue.isEmpty()) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ return true;
|
||||
+ // Paper end - remove streams
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,24 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Zach Brown <1254957+zachbr@users.noreply.github.com>
|
||||
Date: Thu, 16 Apr 2020 20:07:29 -0500
|
||||
Subject: [PATCH] Restrict vanilla teleport command to valid locations
|
||||
|
||||
Fixes GH-3165, GH-3575
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java
|
||||
+++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java
|
||||
@@ -0,0 +0,0 @@ public class TeleportCommand {
|
||||
|
||||
private static void performTeleport(CommandSourceStack source, Entity target, ServerLevel world, double x, double y, double z, Set<ClientboundPlayerPositionPacket.RelativeArgument> movementFlags, float yaw, float pitch, @Nullable TeleportCommand.LookAt facingLocation) throws CommandSyntaxException {
|
||||
BlockPos blockposition = new BlockPos(x, y, z);
|
||||
+ // Paper start - Don't allow teleport command to invalid locations
|
||||
+ if (x <= -30000000 || z <= -30000000 || x > 30000000 || z > 30000000 || y > 30000000 || y <= -30000000) { // Copy/pasta from BaseBlockPosition#isValidLocation
|
||||
+ org.bukkit.Bukkit.getLogger().warning("Refused to teleport " + target.getScoreboardName() + " to " + x + ", " + y + ", " + z);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
if (!Level.isInSpawnableBounds(blockposition)) {
|
||||
throw TeleportCommand.INVALID_POSITION.create();
|
||||
Reference in New Issue
Block a user