More patches

This commit is contained in:
Nassim Jahnke 2024-04-25 13:02:27 +02:00
parent ec4ada852e
commit c9907c650f
No known key found for this signature in database
GPG key ID: EF6771C01F6EF02F
17 changed files with 240 additions and 255 deletions

View file

@ -87,7 +87,7 @@ index 0ffff5329fa2c7833f9ec71528cb7f951cf78109..bf2d91bbb4bf401696f5f5d14a67e392
if (damager != null) {
event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
index 4b94e21d05d6deae75f0c2fb711e43a4c7d06f90..8db431cb9778cd38c8e7ef939a055c4b72232c75 100644
index 4b94e21d05d6deae75f0c2fb711e43a4c7d06f90..662b55f69fb4284cbeb0e08b4b63353d498e0217 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
@@ -271,7 +271,7 @@ public class Creeper extends Monster implements PowerableMob {
@ -95,7 +95,7 @@ index 4b94e21d05d6deae75f0c2fb711e43a4c7d06f90..8db431cb9778cd38c8e7ef939a055c4b
// CraftBukkit end
this.dead = true;
- this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customCausingEntity(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit
+ this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customEventDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit
+ this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customEventDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API
this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
this.spawnLingeringCloud();
// CraftBukkit start

View file

@ -0,0 +1,544 @@
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 6aaed8e8bf8c721fc834da5c76ac72a4c3e92458..4b002e8b75d117b726b0de274a76d3596fce015b 100644
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -92,7 +92,12 @@ 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 bcb6a3b3cd17ce5db9aaf6bd3ec7a0ec1b44b979..4f3cc14d48690bb183d09bb7a5ba1e23e8a0c08a 100644
--- a/src/main/java/net/minecraft/CrashReport.java
+++ b/src/main/java/net/minecraft/CrashReport.java
@@ -234,6 +234,7 @@ 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 4bb0cf4d4eb0b648062c40a8347298002256d39e..affc74c4650ce03aeb723619b63035d539958a8e 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -303,7 +303,7 @@ 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;
@@ -316,6 +316,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public static long currentTickLong = 0L; // Paper - track current tick as a long
public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
+ 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 io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system
@@ -933,6 +936,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// CraftBukkit start
private boolean hasStopped = false;
private boolean hasLoggedStop = false; // Paper - Debugging
+ public volatile boolean hasFullyShutdown = false; // Paper
private final Object stopLock = new Object();
public final boolean hasStopped() {
synchronized (this.stopLock) {
@@ -948,6 +952,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.hasStopped = true;
}
if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
+ // 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
if (this.metricsRecorder.isRecording()) {
this.cancelRecordingMetrics();
@@ -1004,7 +1021,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Spigot end
+ // Paper start - move final shutdown items here
+ LOGGER.info("Flushing Chunk IO");
+ // Paper end - move final shutdown items here
io.papermc.paper.chunk.system.io.RegionFileIOThread.close(true); // Paper - rewrite chunk system
+ // Paper start - move final shutdown items here
+ LOGGER.info("Closing Thread Pool");
+ Util.shutdownExecutors(); // Paper
+ LOGGER.info("Closing Server");
+ try {
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
+ } catch (Exception e) {
+ }
+ io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
+ this.onServerExit();
+ // Paper end - move final shutdown items here
}
public String getLocalIp() {
@@ -1101,6 +1132,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
protected void runServer() {
try {
+ long serverStartTime = Util.getNanos(); // Paper
if (!this.initServer()) {
throw new IllegalStateException("Failed to initialize server");
}
@@ -1110,6 +1142,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.status = this.buildServerStatus();
// 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( this.recentTps, 20 );
// Paper start - further improve server tick loop
@@ -1204,6 +1248,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
}
} 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);
CrashReport crashreport = MinecraftServer.constructOrExtractCrashReport(throwable);
@@ -1228,15 +1278,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.services.profileCache().clearExecutor();
}
- 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
- io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
- this.onServerExit();
+ //io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
+ //this.onServerExit(); // Paper - moved into stop
}
}
@@ -1343,6 +1393,12 @@ 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);
}
@@ -2178,7 +2234,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.worldData.setDataConfiguration(worlddataconfiguration);
this.resources.managers.updateRegistryTags();
this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
- this.getPlayerList().saveAll();
+ // Paper start
+ if (Thread.currentThread() != this.serverThread) {
+ return;
+ }
+ // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
+ for (ServerPlayer player : this.getPlayerList().getPlayers()) {
+ player.getAdvancements().save();
+ }
+ // Paper end
this.getPlayerList().reloadResources();
this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 89c2c26afc5f06c4f57716cadbebabb8854f3635..eb4fc900164d1fb3a78653ae8bc42ea30323f5b7 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -325,7 +325,7 @@ 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, this.overworld()); // CraftBukkit - per-world
}
@@ -459,7 +459,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
// this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
}
- System.exit(0); // CraftBukkit
+ hasFullyShutdown = true; // Paper
+ System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
}
@Override
@@ -840,7 +841,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@Override
public void stopServer() {
super.stopServer();
- Util.shutdownExecutors();
+ //Util.shutdownExecutors(); // Paper - moved into super
SkullBlockEntity.clear();
}
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 65803c0927103e3ae63d8d8616f42090e736508b..3e3bca13578ab9e5b6ca0159383bee0b2a60a86e 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -611,7 +611,7 @@ 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 - Configurable player collision; 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 2510589400b3012b827efcab477c6483d9d55901..43487a9ee202c5b0e5a416519939111f77b3059c 100644
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
@@ -150,6 +150,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
try {
task.run();
} catch (Exception var3) {
+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper
LOGGER.error(LogUtils.FATAL_MARKER, "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 a0d0c7d55158a00e0ca4bfaab67bb5c7f80f2a74..799db7d6229de9e4b7b97da91037dc8fed5b379e 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -927,6 +927,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
try {
tickConsumer.accept(entity);
} catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent block 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 424c4613e202c6ba50fa0de65d2526d400a8e299..2a8609e33716949ff1877b6d10f64a9d7a7c81e9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -1179,6 +1179,7 @@ public class LevelChunk extends ChunkAccess {
gameprofilerfiller.pop();
} catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent block 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/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 3b8a2de79c79302626f43f320b15bae52416cc37..c097f5d5fbd51cbbc01bbd54101905c59b3f3a4c 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -187,6 +187,36 @@ 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) {
@@ -298,8 +328,65 @@ 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(net.minecraft.world.level.lighting.LayerLightEventListener.DummyLightLayerEventListener.class.getName());
+ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.class.getName());
+ tryPreloadClass(net.minecraft.util.ExceptionCollector.class.getName());
+ tryPreloadClass(io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData.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 c6e8441e299f477ddb22c1ce2618710763978f1a..e8e93538dfd71de86515d9405f728db1631e949a 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -12,11 +12,27 @@ 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
+ server.forceTicks = true;
this.server.close();
+ while (!server.hasFullyShutdown) Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ // Paper end
} finally {
+ org.apache.logging.log4j.LogManager.shutdown(); // Paper
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 e3b262add194a126e731c68e68f3139a00cacacb..da7d5efd76c9ef92e9ce22860fec791890a687be 100644
--- a/src/main/java/org/spigotmc/RestartCommand.java
+++ b/src/main/java/org/spigotmc/RestartCommand.java
@@ -138,7 +138,7 @@ 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 9e5d08f57aa448552d100ca892c211d44441ef68..577f29519156f33b49ee319a64a52249630e28d8 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -11,6 +11,7 @@ import org.bukkit.Bukkit;
public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system
{
+ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper
private static WatchdogThread instance;
private long timeoutTime;
private boolean restart;
@@ -39,6 +40,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
{
if ( WatchdogThread.instance == null )
{
+ if (timeoutTime <= 0) timeoutTime = 300; // Paper
WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
WatchdogThread.instance.start();
} else
@@ -70,12 +72,13 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
// 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
@@ -136,9 +139,24 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
if ( isLongTimeout )
{
- if ( this.restart && !MinecraftServer.getServer().hasStopped() )
+ if ( !server.hasStopped() )
{
- RestartCommand.restart();
+ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
+ 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 637d64da9938e51a97338b9253b43889585c67bb..d2a75850af9c6ad2aca66a5f994f1b587d73eac4 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="WARN">
+<Configuration status="WARN" shutdownHook="disable">
<Appenders>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />

View file

@ -0,0 +1,123 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 3 May 2020 22:35:09 -0400
Subject: [PATCH] Optimize Voxel Shape Merging
This method shows up as super hot in profiler, and also a high "self" time.
Upon analyzing, it appears most usages of this method fall down to the final
else statement of the nasty ternary.
Upon even further analyzation, it appears then the majority of those have a
consistent list 1.... One with Infinity head and Tails.
First optimization is to detect these infinite states and immediately return that
VoxelShapeMergerList so we can avoid testing the rest for most cases.
Break the method into 2 to help the JVM promote inlining of this fast path.
Then it was also noticed that VoxelShapeMergerList constructor is also a hotspot
with a high self time...
Well, knowing that in most cases our list 1 is actualy the same value, it allows
us to know that with an infinite list1, the result on the merger is essentially
list2 as the final values.
This let us analyze the 2 potential states (Infinite with 2 sources or 4 sources)
and compute a deterministic result for the MergerList values.
Additionally, this lets us avoid even allocating new objects for this too, further
reducing memory usage.
diff --git a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
index e164c524aef4fa81fe96ac43454eecff1c38b9c1..9cfbbc61fcfc678f0988d6d45c7994d128051744 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
@@ -10,12 +10,33 @@ public class IndirectMerger implements IndexMerger {
private final int[] firstIndices;
private final int[] secondIndices;
private final int resultLength;
+ // Paper start
+ private static final int[] INFINITE_B_1 = new int[]{1, 1};
+ private static final int[] INFINITE_B_0 = new int[]{0, 0};
+ private static final int[] INFINITE_C = new int[]{0, 1};
+ // Paper end
public IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) {
double d = Double.NaN;
int i = first.size();
int j = second.size();
int k = i + j;
+ // Paper start - optimize common path of infinity doublelist
+ int size = first.size();
+ double tail = first.getDouble(size - 1);
+ double head = first.getDouble(0);
+ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) {
+ this.result = second.toDoubleArray();
+ this.resultLength = second.size();
+ if (size == 2) {
+ this.firstIndices = INFINITE_B_0;
+ } else {
+ this.firstIndices = INFINITE_B_1;
+ }
+ this.secondIndices = INFINITE_C;
+ return;
+ }
+ // Paper end
this.result = new double[k];
this.firstIndices = new int[k];
this.secondIndices = new int[k];
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 0fdd2cdd8d215ca1523eda8ad7316cdd5f41a6b5..86df4ef44d0a5107ee929dfd40d8ccb0779e8bfc 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
@@ -286,9 +286,21 @@ public final class Shapes {
}
@VisibleForTesting
- protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
+ private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // Paper - private
+ // Paper start - fast track the most common scenario
+ // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause
+ // This is actually the most common path, so jump to it straight away
+ if (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) {
+ return new IndirectMerger(first, second, includeFirst, includeSecond);
+ }
+ // Split out rest to hopefully inline the above
+ return lessCommonMerge(size, first, second, includeFirst, includeSecond);
+ }
+
+ private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
int i = first.size() - 1;
int j = second.size() - 1;
+ // Paper note - Rewrite below as optimized order if instead of nasty ternary
if (first instanceof CubePointRange && second instanceof CubePointRange) {
long l = lcm(i, j);
if ((long)size * l <= 256L) {
@@ -296,15 +308,22 @@ public final class Shapes {
}
}
- if (first.getDouble(i) < second.getDouble(0) - 1.0E-7) {
+ // Paper start - Identical happens more often than Disjoint
+ if (i == j && Objects.equals(first, second)) {
+ if (first instanceof IdenticalMerger) {
+ return (IndexMerger) first;
+ } else if (second instanceof IdenticalMerger) {
+ return (IndexMerger) second;
+ }
+ return new IdenticalMerger(first);
+ } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7) {
return new NonOverlappingMerger(first, second, false);
} else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7) {
return new NonOverlappingMerger(second, first, true);
} else {
- return (IndexMerger)(i == j && Objects.equals(first, second)
- ? new IdenticalMerger(first)
- : new IndirectMerger(first, second, includeFirst, includeSecond));
+ return new IndirectMerger(first, second, includeFirst, includeSecond);
}
+ // Paper end
}
public interface DoubleLineConsumer {

View file

@ -0,0 +1,173 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cryptite <cryptite@gmail.com>
Date: Tue, 27 Jun 2023 11:35:52 -0500
Subject: [PATCH] Write SavedData IO async
Co-Authored-By: Shane Freeder <theboyetronic@gmail.com>
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index c7b7f153895a4b95b2071a31db00c9c4b69fa094..0b7a38b9e92b19345a34c6226413a9b133264077 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -447,6 +447,13 @@ public class ServerChunkCache extends ChunkSource {
public void close(boolean save) { // Paper - rewrite chunk system
this.level.chunkTaskScheduler.chunkHolderManager.close(save, true); // Paper - rewrite chunk system
+ // Paper start - Write SavedData IO async
+ try {
+ this.dataStorage.close();
+ } catch (IOException exception) {
+ LOGGER.error("Failed to close persistent world data", exception);
+ }
+ // Paper end - Write SavedData IO async
}
// CraftBukkit start - modelled on below
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index ed8e875fff01c6b464fbaefbb0a3f417f9d67a72..aba5f694b25507c9ab2e214bc1f25e0a895e8a95 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1324,7 +1324,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) {
if (doFull) {
- this.saveLevelData();
+ this.saveLevelData(true); // Paper - Write SavedData IO async
}
this.timings.worldSaveChunks.startTiming(); // Paper
@@ -1360,7 +1360,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
}
- this.saveLevelData();
+ this.saveLevelData(!close); // Paper - Write SavedData IO async
if (progressListener != null) {
progressListener.progressStage(Component.translatable("menu.savingChunks"));
}
@@ -1383,12 +1383,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
// CraftBukkit end
}
- private void saveLevelData() {
+ private void saveLevelData(boolean async) { // Paper - Write SavedData IO async
if (this.dragonFight != null) {
this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
}
- this.getChunkSource().getDataStorage().save();
+ this.getChunkSource().getDataStorage().save(async); // Paper - Write SavedData IO async
}
public <T extends Entity> List<? extends T> getEntities(EntityTypeTest<Entity, T> filter, Predicate<? super T> predicate) {
diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
index 7984f17cd9c4cef8100909b6c33b3144c8096fcf..868c9bbb12c8cfe76abb62774cf08102b727063b 100644
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@@ -116,7 +116,13 @@ public class WorldUpgrader {
(new WorldUpgrader.PoiUpgrader(this)).upgrade();
WorldUpgrader.LOGGER.info("Upgrading blocks");
(new WorldUpgrader.ChunkUpgrader()).upgrade();
- this.overworldDataStorage.save();
+ // Paper start - Write SavedData IO async
+ try {
+ this.overworldDataStorage.close();
+ } catch (final IOException e) {
+ LOGGER.error("Failed to close persistent world data", e);
+ }
+ // Paper end - Write SavedData IO async
i = Util.getMillis() - i;
WorldUpgrader.LOGGER.info("World optimizaton finished after {} seconds", i / 1000L);
this.finished = true;
diff --git a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
index 9cc3850bb70dfbcf342651360314a19fd9ea3ecc..4cbb943b702baaeb8bfd2b558cc848e719cf095d 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
@@ -30,20 +30,36 @@ public abstract class SavedData {
return this.dirty;
}
+ // Paper start - Write SavedData IO async - joining is evil, but we assume the old blocking behavior here just for safety
+ @io.papermc.paper.annotation.DoNotUse
public void save(File file, HolderLookup.Provider registryLookup) {
+ save(file, registryLookup, null).join();
+ }
+
+ public java.util.concurrent.CompletableFuture<Void> save(File file, HolderLookup.Provider registryLookup, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) {
+ // Paper end - Write SavedData IO async
if (this.isDirty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.put("data", this.save(new CompoundTag(), registryLookup));
NbtUtils.addCurrentDataVersion(compoundTag);
+ Runnable writeRunnable = () -> { // Paper - Write SavedData IO async
try {
NbtIo.writeCompressed(compoundTag, file.toPath());
} catch (IOException var5) {
LOGGER.error("Could not save data {}", this, var5);
}
+ }; // Paper - Write SavedData IO async
this.setDirty(false);
+ // Paper start - Write SavedData IO async
+ if (ioExecutor == null) {
+ return java.util.concurrent.CompletableFuture.runAsync(writeRunnable); // No executor, just use common pool
+ }
+ return java.util.concurrent.CompletableFuture.runAsync(writeRunnable, ioExecutor);
}
+ return java.util.concurrent.CompletableFuture.completedFuture(null);
+ // Paper end - Write SavedData IO async
}
public static record Factory<T extends SavedData>(
diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
index 7d98d2911e9d6ec429ce7df1f1f2650c7ea32325..6e23e69abd56eeda3b52a22019e1b74ae10682e7 100644
--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
+++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
@@ -23,17 +23,19 @@ import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.saveddata.SavedData;
import org.slf4j.Logger;
-public class DimensionDataStorage {
+public class DimensionDataStorage implements java.io.Closeable { // Paper - Write SavedData IO async
private static final Logger LOGGER = LogUtils.getLogger();
public final Map<String, SavedData> cache = Maps.newHashMap();
private final DataFixer fixerUpper;
private final HolderLookup.Provider registries;
private final File dataFolder;
+ protected final java.util.concurrent.ExecutorService ioExecutor; // Paper - Write SavedData IO async
public DimensionDataStorage(File directory, DataFixer dataFixer, HolderLookup.Provider registryLookup) {
this.fixerUpper = dataFixer;
this.dataFolder = directory;
this.registries = registryLookup;
+ this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + dataFolder.getParent() + " - %d").setDaemon(true).build()); // Paper - Write SavedData IO async
}
private File getDataFile(String id) {
@@ -123,10 +125,23 @@ public class DimensionDataStorage {
return bl;
}
- public void save() {
+ // Paper start - Write SavedData IO async
+ @Override
+ public void close() throws IOException {
+ save(false);
+ this.ioExecutor.shutdown();
+ }
+ // Paper end - Write SavedData IO async
+
+ public void save(boolean async) { // Paper - Write SavedData IO async
this.cache.forEach((id, state) -> {
if (state != null) {
- state.save(this.getDataFile(id), this.registries);
+ // Paper start - Write SavedData IO async
+ final java.util.concurrent.CompletableFuture<Void> save = state.save(this.getDataFile(id), this.registries, this.ioExecutor);
+ if (!async) {
+ save.join();
+ }
+ // Paper end - Write SavedData IO async
}
});
}

View file

@ -0,0 +1,169 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 13 May 2020 23:01:26 -0400
Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed
This fixes exploits that let players destroy bedrock by Pistons, explosions
and Mushrooom/Tree generation.
These blocks are designed to not be broken except by creative players/commands.
So protect them from a multitude of methods of destroying them.
A config is provided if you rather let players use these exploits, and let
them destroy the worlds End Portals and get on top of the nether easy.
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 550c0cf4d84afc0cd6b2675bdc6aa2282d87e312..6bd6533e314b64d3512cbb46f5174a4956c323b2 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -192,6 +192,7 @@ public class Explosion {
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
BlockPos blockposition = BlockPos.containing(d4, d5, d6);
BlockState iblockdata = this.level.getBlockState(blockposition);
+ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
if (!this.level.isInWorldBounds(blockposition)) {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 799db7d6229de9e4b7b97da91037dc8fed5b379e..232ac315fa1ed0e6e9ff4882d3378745804e4e1c 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -534,6 +534,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+ BlockState type = getBlockState(pos);
+ if (!type.isDestroyable()) return false;
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
CraftBlockState blockstate = this.capturedBlockStates.get(pos);
if (blockstate == null) {
blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags);
diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
index 7f0c0ca49e7575c18935b71e3180d112440289f7..054593fc0b8d13f6bf449cc20a1f7ddfd5f1d1f0 100644
--- a/src/main/java/net/minecraft/world/level/block/Block.java
+++ b/src/main/java/net/minecraft/world/level/block/Block.java
@@ -90,6 +90,19 @@ public class Block extends BlockBehaviour implements ItemLike {
protected final StateDefinition<Block, BlockState> stateDefinition;
private BlockState defaultBlockState;
// Paper start
+ public final boolean isDestroyable() {
+ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits ||
+ this != Blocks.BEDROCK &&
+ this != Blocks.END_PORTAL_FRAME &&
+ this != Blocks.END_PORTAL &&
+ this != Blocks.END_GATEWAY &&
+ this != Blocks.COMMAND_BLOCK &&
+ this != Blocks.REPEATING_COMMAND_BLOCK &&
+ this != Blocks.CHAIN_COMMAND_BLOCK &&
+ this != Blocks.BARRIER &&
+ this != Blocks.STRUCTURE_BLOCK &&
+ this != Blocks.JIGSAW;
+ }
public co.aikar.timings.Timing timing;
public co.aikar.timings.Timing getTiming() {
if (timing == null) {
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
index e6bfbe2588e0c2a1be14e38d654e889d392ad4db..e0c62227b279a5fe0f3868fbf9ce8c78c515a09c 100644
--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
@@ -213,6 +213,12 @@ public class PistonBaseBlock extends DirectionalBlock {
@Override
protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING);
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) {
+ return false;
+ }
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true);
if (!world.isClientSide) {
@@ -253,7 +259,7 @@ public class PistonBaseBlock extends DirectionalBlock {
}
// Paper end - Fix sticky pistons and BlockPistonRetractEvent
world.setBlock(pos, iblockdata2, 20);
- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true));
+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change
world.blockUpdated(pos, iblockdata2.getBlock());
iblockdata2.updateNeighbourShapes(world, pos, 2);
if (this.isSticky) {
@@ -289,7 +295,14 @@ public class PistonBaseBlock extends DirectionalBlock {
}
}
} else {
- world.removeBlock(pos.relative(enumdirection), false);
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks
+ BlockPos headPos = pos.relative(enumdirection);
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston.
+ world.removeBlock(headPos, false);
+ } else {
+ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync
+ }
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
}
world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F);
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 0cc12c418d18c5c2e927b93907daea3e0f662035..f863f3a553e26af1fb656622da052505e6ef1c01 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -174,7 +174,7 @@ public abstract class BlockBehaviour implements FeatureElement {
}
protected void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> stackMerger) {
- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) {
+ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed
Block block = state.getBlock();
boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
@@ -254,7 +254,7 @@ public abstract class BlockBehaviour implements FeatureElement {
}
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem()));
+ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
}
protected boolean canBeReplaced(BlockState state, Fluid fluid) {
@@ -896,6 +896,12 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.legacySolid;
}
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+ public final boolean isDestroyable() {
+ return getBlock().isDestroyable();
+ }
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+
public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType<?> type) {
return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type);
}
@@ -999,7 +1005,7 @@ public abstract class BlockBehaviour implements FeatureElement {
}
public PushReaction getPistonPushReaction() {
- return this.pushReaction;
+ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
}
public boolean isSolidRender(BlockGetter world, BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
index 03dd833d61d5152af3032f23dd1fc4c75da9bc4f..a61959700d5e00739a79eaa617ac383160335f26 100644
--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
@@ -221,6 +221,13 @@ public class PortalForcer {
for (int j = -1; j < 3; ++j) {
for (int k = -1; k < 4; ++k) {
temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal);
+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) {
+ if (!this.level.getBlockState(temp).isDestroyable()) {
+ return false;
+ }
+ }
+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
if (k < 0 && !this.level.getBlockState(temp).isSolid()) {
return false;
}

View file

@ -0,0 +1,341 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 20:18:05 -0700
Subject: [PATCH] Use distance map to optimise entity tracker
Use the distance map to find candidate players for tracking.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 2ae2f36ef3d78ee4859c09096d516eeb3ffb02a0..9bb56d235a7614cefcd0551a455f9c4ddadb1db0 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -149,6 +149,23 @@ 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 - use distance map to optimise tracker
+ public static boolean isLegacyTrackingEntity(Entity entity) {
+ return entity.isLegacyTrackingEntity;
+ }
+
+ // inlined EnumMap, TrackingRange.TrackingRangeType
+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values();
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps;
+ final int[] entityTrackerTrackRanges;
+ public final int getEntityTrackerRange(final int ordinal) {
+ return this.entityTrackerTrackRanges[ordinal];
+ }
+
+ private int convertSpigotRangeToVanilla(final int vanilla) {
+ return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla);
+ }
+ // Paper end - use distance map to optimise tracker
void addPlayerToDistanceMaps(ServerPlayer player) {
int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
@@ -156,6 +173,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Note: players need to be explicitly added to distance maps before they can be updated
this.nearbyPlayers.addPlayer(player);
this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader
+ // Paper start - use distance map to optimise entity tracker
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
+ int trackRange = this.entityTrackerTrackRanges[i];
+
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
+ }
+ // Paper end - use distance map to optimise entity tracker
}
void removePlayerFromDistanceMaps(ServerPlayer player) {
@@ -164,6 +189,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Note: players need to be explicitly added to distance maps before they can be updated
this.nearbyPlayers.removePlayer(player);
this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader
+ // Paper start - use distance map to optimise tracker
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+ this.playerEntityTrackerTrackMaps[i].remove(player);
+ }
+ // Paper end - use distance map to optimise tracker
}
void updateMaps(ServerPlayer player) {
@@ -172,6 +202,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Note: players need to be explicitly added to distance maps before they can be updated
this.nearbyPlayers.tickPlayer(player);
this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader
+ // Paper start - use distance map to optimise entity tracker
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
+ int trackRange = this.entityTrackerTrackRanges[i];
+
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
+ }
+ // Paper end - use distance map to optimise entity tracker
}
// Paper end
// Paper start
@@ -258,6 +296,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.add(this.dataRegionManager);
this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level);
// Paper end
+ // Paper start - use distance map to optimise entity tracker
+ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
+ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
+
+ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig;
+
+ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) {
+ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal];
+ int configuredSpigotValue;
+ switch (trackingRangeType) {
+ case PLAYER:
+ configuredSpigotValue = spigotWorldConfig.playerTrackingRange;
+ break;
+ case ANIMAL:
+ configuredSpigotValue = spigotWorldConfig.animalTrackingRange;
+ break;
+ case MONSTER:
+ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange;
+ break;
+ case MISC:
+ configuredSpigotValue = spigotWorldConfig.miscTrackingRange;
+ break;
+ case OTHER:
+ configuredSpigotValue = spigotWorldConfig.otherTrackingRange;
+ break;
+ case ENDERDRAGON:
+ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16;
+ break;
+ case DISPLAY:
+ configuredSpigotValue = spigotWorldConfig.displayTrackingRange;
+ break;
+ default:
+ throw new IllegalStateException("Missing case for enum " + trackingRangeType);
+ }
+ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue);
+
+ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0);
+ this.entityTrackerTrackRanges[ordinal] = trackRange;
+
+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ }
+ // Paper end - use distance map to optimise entity tracker
}
// Paper start
@@ -951,17 +1031,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void move(ServerPlayer player) {
- ObjectIterator objectiterator = this.entityMap.values().iterator();
-
- while (objectiterator.hasNext()) {
- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
-
- if (playerchunkmap_entitytracker.entity == player) {
- playerchunkmap_entitytracker.updatePlayers(this.level.players());
- } else {
- playerchunkmap_entitytracker.updatePlayer(player);
- }
- }
+ // Paper - delay this logic for the entity tracker tick, no need to duplicate it
SectionPos sectionposition = player.getLastSectionPos();
SectionPos sectionposition1 = SectionPos.of((EntityAccess) player);
@@ -1038,7 +1108,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
- playerchunkmap_entitytracker.updatePlayers(this.level.players());
+ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players
if (entity instanceof ServerPlayer) {
ServerPlayer entityplayer = (ServerPlayer) entity;
@@ -1080,9 +1150,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
entity.tracker = null; // Paper - We're no longer tracked
}
- protected void tick() {
- // Paper - replaced by PlayerChunkLoader
+ // Paper start - optimised tracker
+ private final void processTrackQueue() {
+ this.level.timings.tracker1.startTiming();
+ try {
+ for (TrackedEntity tracker : this.entityMap.values()) {
+ // update tracker entry
+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange());
+ }
+ } finally {
+ this.level.timings.tracker1.stopTiming();
+ }
+
+
+ this.level.timings.tracker2.startTiming();
+ try {
+ for (TrackedEntity tracker : this.entityMap.values()) {
+ tracker.serverEntity.sendChanges();
+ }
+ } finally {
+ this.level.timings.tracker2.stopTiming();
+ }
+ }
+ // Paper end - optimised tracker
+ protected void tick() {
+ // Paper start - optimized tracker
+ if (true) {
+ this.processTrackQueue();
+ return;
+ }
+ // Paper end - optimized tracker
List<ServerPlayer> list = Lists.newArrayList();
List<ServerPlayer> list1 = this.level.players();
ObjectIterator objectiterator = this.entityMap.values().iterator();
@@ -1230,6 +1328,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
}
+ // Paper start - use distance map to optimise tracker
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates;
+
+ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
+ this.lastTrackerCandidates = newTrackerCandidates;
+
+ if (newTrackerCandidates != null) {
+ Object[] rawData = newTrackerCandidates.getBackingSet();
+ for (int i = 0, len = rawData.length; i < len; ++i) {
+ Object raw = rawData[i];
+ if (!(raw instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)raw;
+ this.updatePlayer(player);
+ }
+ }
+
+ if (oldTrackerCandidates == newTrackerCandidates) {
+ // this is likely the case.
+ // means there has been no range changes, so we can just use the above for tracking.
+ return;
+ }
+
+ // stuff could have been removed, so we need to check the trackedPlayers set
+ // for players that were removed
+
+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME
+ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) {
+ this.updatePlayer(conn.getPlayer());
+ }
+ }
+ }
+ // Paper end - use distance map to optimise tracker
+
public boolean equals(Object object) {
return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false;
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index b57644317b5085d74d11ac6ba858c3747d703a47..257943b4c984d6faee29eca17c8f951e7f43168b 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -59,6 +59,7 @@ import net.minecraft.network.syncher.SyncedDataHolder;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
+import io.papermc.paper.util.MCUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -471,6 +472,38 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.teleportTo(worldserver, null);
}
// Paper end - make end portalling safe
+ // Paper start - optimise entity tracking
+ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this);
+
+ public boolean isLegacyTrackingEntity = false;
+
+ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) {
+ this.isLegacyTrackingEntity = isLegacyTrackingEntity;
+ }
+
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() {
+ // determine highest range of passengers
+ if (this.passengers.isEmpty()) {
+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()]
+ .getObjectsInRange(MCUtil.getCoordinateKey(this));
+ }
+ Iterable<Entity> passengers = this.getIndirectPassengers();
+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap;
+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType;
+ int range = chunkMap.getEntityTrackerRange(type.ordinal());
+
+ for (Entity passenger : passengers) {
+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
+ if (passengerRange > range) {
+ type = passengerType;
+ range = passengerRange;
+ }
+ }
+
+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this));
+ }
+ // Paper end - optimise entity tracking
public float getBukkitYaw() {
return this.yRot;
}
diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java
index bb06f89a29f30144e7e2113e088a503db006a83c..e4425b242fe73d1fd2bd10c313aa16925432329f 100644
--- a/src/main/java/org/spigotmc/TrackingRange.java
+++ b/src/main/java/org/spigotmc/TrackingRange.java
@@ -55,4 +55,48 @@ public class TrackingRange
return config.otherTrackingRange;
}
}
+
+ // Paper start - optimise entity tracking
+ // copied from above, TODO check on update
+ public static TrackingRangeType getTrackingRangeType(Entity entity)
+ {
+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt
+ if ( entity instanceof ServerPlayer )
+ {
+ return TrackingRangeType.PLAYER;
+ // Paper start - Simplify and set water mobs to animal tracking range
+ }
+ switch (entity.activationType) {
+ case RAIDER:
+ case MONSTER:
+ case FLYING_MONSTER:
+ return TrackingRangeType.MONSTER;
+ case WATER:
+ case VILLAGER:
+ case ANIMAL:
+ return TrackingRangeType.ANIMAL;
+ case MISC:
+ }
+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb )
+ // Paper end
+ {
+ return TrackingRangeType.MISC;
+ } else if (entity instanceof Display) {
+ return TrackingRangeType.DISPLAY;
+ } else
+ {
+ return TrackingRangeType.OTHER;
+ }
+ }
+
+ public static enum TrackingRangeType {
+ PLAYER,
+ ANIMAL,
+ MONSTER,
+ MISC,
+ OTHER,
+ ENDERDRAGON,
+ DISPLAY;
+ }
+ // Paper end - optimise entity tracking
}

View file

@ -0,0 +1,213 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 4 Jun 2020 02:24:49 -0400
Subject: [PATCH] Optimize Bit Operations by inlining
Inline bit operations and reduce instruction count to make these hot
operations faster
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index 4144c872fbd89d22827ad1f586e9a8d63a39ed46..665e88b2dedf9d5bb50914d5f3d377f2d19f40b0 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -50,15 +50,16 @@ public class BlockPos extends Vec3i {
};
private static final Logger LOGGER = LogUtils.getLogger();
public static final BlockPos ZERO = new BlockPos(0, 0, 0);
- private static final int PACKED_X_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000));
- private static final int PACKED_Z_LENGTH = PACKED_X_LENGTH;
- public static final int PACKED_Y_LENGTH = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH;
- private static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L;
- private static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L;
- private static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L;
- private static final int Y_OFFSET = 0;
- private static final int Z_OFFSET = PACKED_Y_LENGTH;
- private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH;
+ // Paper start - Optimize Bit Operations by inlining
+ private static final int PACKED_X_LENGTH = 26;
+ private static final int PACKED_Z_LENGTH = 26;
+ public static final int PACKED_Y_LENGTH = 12;
+ private static final long PACKED_X_MASK = 67108863;
+ private static final long PACKED_Y_MASK = 4095;
+ private static final long PACKED_Z_MASK = 67108863;
+ private static final int Z_OFFSET = 12;
+ private static final int X_OFFSET = 38;
+ // Paper end - Optimize Bit Operations by inlining
public BlockPos(int x, int y, int z) {
super(x, y, z);
@@ -68,28 +69,29 @@ public class BlockPos extends Vec3i {
this(pos.getX(), pos.getY(), pos.getZ());
}
+ public static long getAdjacent(int baseX, int baseY, int baseZ, Direction enumdirection) { return asLong(baseX + enumdirection.getStepX(), baseY + enumdirection.getStepY(), baseZ + enumdirection.getStepZ()); } // Paper
public static long offset(long value, Direction direction) {
return offset(value, direction.getStepX(), direction.getStepY(), direction.getStepZ());
}
public static long offset(long value, int x, int y, int z) {
- return asLong(getX(value) + x, getY(value) + y, getZ(value) + z);
+ return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z); // Paper - simplify/inline
}
public static int getX(long packedPos) {
- return (int)(packedPos << 64 - X_OFFSET - PACKED_X_LENGTH >> 64 - PACKED_X_LENGTH);
+ return (int) (packedPos >> 38); // Paper - simplify/inline
}
public static int getY(long packedPos) {
- return (int)(packedPos << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH);
+ return (int) ((packedPos << 52) >> 52); // Paper - simplify/inline
}
public static int getZ(long packedPos) {
- return (int)(packedPos << 64 - Z_OFFSET - PACKED_Z_LENGTH >> 64 - PACKED_Z_LENGTH);
+ return (int) ((packedPos << 26) >> 38); // Paper - simplify/inline
}
public static BlockPos of(long packedPos) {
- return new BlockPos(getX(packedPos), getY(packedPos), getZ(packedPos));
+ return new BlockPos((int) (packedPos >> 38), (int) ((packedPos << 52) >> 52), (int) ((packedPos << 26) >> 38)); // Paper - simplify/inline
}
public static BlockPos containing(double x, double y, double z) {
@@ -113,10 +115,7 @@ public class BlockPos extends Vec3i {
}
public static long asLong(int x, int y, int z) {
- long l = 0L;
- l |= ((long)x & PACKED_X_MASK) << X_OFFSET;
- l |= ((long)y & PACKED_Y_MASK) << 0;
- return l | ((long)z & PACKED_Z_MASK) << Z_OFFSET;
+ return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); // Paper - inline constants and simplify
}
public static long getFlatIndex(long y) {
diff --git a/src/main/java/net/minecraft/core/SectionPos.java b/src/main/java/net/minecraft/core/SectionPos.java
index 27e0d53d5893a13a340deddc93a1128968db7e5b..fe3577e533fb829c85fd4881b1bcca3b70aaf1a5 100644
--- a/src/main/java/net/minecraft/core/SectionPos.java
+++ b/src/main/java/net/minecraft/core/SectionPos.java
@@ -38,7 +38,7 @@ public class SectionPos extends Vec3i {
}
public static SectionPos of(BlockPos pos) {
- return new SectionPos(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ()));
+ return new SectionPos(pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4); // Paper
}
public static SectionPos of(ChunkPos chunkPos, int y) {
@@ -54,7 +54,7 @@ public class SectionPos extends Vec3i {
}
public static SectionPos of(long packed) {
- return new SectionPos(x(packed), y(packed), z(packed));
+ return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); // Paper
}
public static SectionPos bottomOf(ChunkAccess chunk) {
@@ -65,8 +65,16 @@ public class SectionPos extends Vec3i {
return offset(packed, direction.getStepX(), direction.getStepY(), direction.getStepZ());
}
+ // Paper start
+ public static long getAdjacentFromBlockPos(int x, int y, int z, Direction enumdirection) {
+ return (((long) ((x >> 4) + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getStepY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getStepZ()) & 4194303L) << 20);
+ }
+ public static long getAdjacentFromSectionPos(int x, int y, int z, Direction enumdirection) {
+ return (((long) (x + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getStepY()) & 1048575L)) | (((long) (z + enumdirection.getStepZ()) & 4194303L) << 20);
+ }
+ // Paper end
public static long offset(long packed, int x, int y, int z) {
- return asLong(x(packed) + x, y(packed) + y, z(packed) + z);
+ return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20); // Simplify to reduce instruction count
}
public static int posToSectionCoord(double coord) {
@@ -86,10 +94,7 @@ public class SectionPos extends Vec3i {
}
public static short sectionRelativePos(BlockPos pos) {
- int i = sectionRelative(pos.getX());
- int j = sectionRelative(pos.getY());
- int k = sectionRelative(pos.getZ());
- return (short)(i << 8 | k << 4 | j << 0);
+ return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15); // Paper - simplify/inline
}
public static int sectionRelativeX(short packedLocalPos) {
@@ -152,16 +157,16 @@ public class SectionPos extends Vec3i {
return this.getZ();
}
- public int minBlockX() {
- return sectionToBlockCoord(this.x());
+ public final int minBlockX() { // Paper - make final
+ return this.getX() << 4; // Paper - inline
}
- public int minBlockY() {
- return sectionToBlockCoord(this.y());
+ public final int minBlockY() { // Paper - make final
+ return this.getY() << 4; // Paper - inline
}
- public int minBlockZ() {
- return sectionToBlockCoord(this.z());
+ public int minBlockZ() { // Paper - make final
+ return this.getZ() << 4; // Paper - inline
}
public int maxBlockX() {
@@ -177,7 +182,8 @@ public class SectionPos extends Vec3i {
}
public static long blockToSection(long blockPos) {
- return asLong(blockToSectionCoord(BlockPos.getX(blockPos)), blockToSectionCoord(BlockPos.getY(blockPos)), blockToSectionCoord(BlockPos.getZ(blockPos)));
+ // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i)));
+ return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count
}
public static long getZeroNode(int x, int z) {
@@ -205,15 +211,18 @@ public class SectionPos extends Vec3i {
return asLong(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ()));
}
+ // Paper start
+ public static long blockPosAsSectionLong(int i, int j, int k) {
+ return (((long) (i >> 4) & 4194303L) << 42) | (((long) (j >> 4) & 1048575L)) | (((long) (k >> 4) & 4194303L) << 20);
+ }
+ // Paper end
+
public static long asLong(int x, int y, int z) {
- long l = 0L;
- l |= ((long)x & 4194303L) << 42;
- l |= ((long)y & 1048575L) << 0;
- return l | ((long)z & 4194303L) << 20;
+ return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20); // Paper - Simplify to reduce instruction count
}
public long asLong() {
- return asLong(this.x(), this.y(), this.z());
+ return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count
}
@Override
@@ -226,16 +235,11 @@ public class SectionPos extends Vec3i {
}
public static Stream<SectionPos> cube(SectionPos center, int radius) {
- int i = center.x();
- int j = center.y();
- int k = center.z();
- return betweenClosedStream(i - radius, j - radius, k - radius, i + radius, j + radius, k + radius);
+ return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius); // Paper - simplify/inline
}
public static Stream<SectionPos> aroundChunk(ChunkPos center, int radius, int minY, int maxY) {
- int i = center.x;
- int j = center.z;
- return betweenClosedStream(i - radius, minY, j - radius, i + radius, maxY - 1, j + radius);
+ return betweenClosedStream(center.x - radius, 0, center.z - radius, center.x + radius, 15, center.z + radius); // Paper - simplify/inline
}
public static Stream<SectionPos> betweenClosedStream(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {

View file

@ -0,0 +1,215 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: JRoy <joshroy126@gmail.com>
Date: Wed, 1 Jul 2020 18:01:49 -0400
Subject: [PATCH] Remove streams from hot code
Co-authored-by: Bjarne Koll <lynxplay101@gmail.com>
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
index 6a270c9adb044a6e0b1c8e09b9106d51989fd761..d4581127366736c54f74e4ef7479236b18fb487d 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
@@ -57,7 +57,7 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
if (this.hasRequiredMemories(entity)) {
this.status = Behavior.Status.RUNNING;
this.orderPolicy.apply(this.behaviors);
- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time);
+ this.runningPolicy.apply(this.behaviors, world, entity, time); // Paper - Perf: Remove streams from hot code
return true;
} else {
return false;
@@ -66,7 +66,13 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
@Override
public final void tickOrStop(ServerLevel world, E entity, long time) {
- this.behaviors.stream().filter(task -> task.getStatus() == Behavior.Status.RUNNING).forEach(task -> task.tickOrStop(world, entity, time));
+ // Paper start - Perf: Remove streams from hot code
+ for (final BehaviorControl<? super E> task : this.behaviors) {
+ if (task.getStatus() == Behavior.Status.RUNNING) {
+ task.tickOrStop(world, entity, time);
+ }
+ }
+ // Paper end - Perf: Remove streams from hot code
if (this.behaviors.stream().noneMatch(task -> task.getStatus() == Behavior.Status.RUNNING)) {
this.doStop(world, entity, time);
}
@@ -75,8 +81,16 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
@Override
public final void doStop(ServerLevel world, E entity, long time) {
this.status = Behavior.Status.STOPPED;
- this.behaviors.stream().filter(task -> task.getStatus() == Behavior.Status.RUNNING).forEach(task -> task.doStop(world, entity, time));
- this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory);
+ // Paper start - Perf: Remove streams from hot code
+ for (final BehaviorControl<? super E> task : this.behaviors) {
+ if (task.getStatus() == Behavior.Status.RUNNING) {
+ task.doStop(world, entity, time);
+ }
+ }
+ for (final MemoryModuleType<?> exitErasedMemory : this.exitErasedMemories) {
+ entity.getBrain().eraseMemory(exitErasedMemory);
+ }
+ // Paper end - Perf: Remove streams from hot code
}
@Override
@@ -111,18 +125,30 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
public static enum RunningPolicy {
RUN_ONE {
+ // Paper start - Perf: Remove streams from hot code
@Override
- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
- tasks.filter(task -> task.getStatus() == Behavior.Status.STOPPED).filter(task -> task.tryStart(world, entity, time)).findFirst();
+ public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
+ for (final BehaviorControl<? super E> task : tasks) {
+ if (task.getStatus() == Behavior.Status.STOPPED && task.tryStart(world, entity, time)) {
+ break;
+ }
+ }
+ // Paper end - Perf: Remove streams from hot code
}
},
TRY_ALL {
+ // Paper start - Perf: Remove streams from hot code
@Override
- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
- tasks.filter(task -> task.getStatus() == Behavior.Status.STOPPED).forEach(task -> task.tryStart(world, entity, time));
+ public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
+ for (final BehaviorControl<? super E> task : tasks) {
+ if (task.getStatus() == Behavior.Status.STOPPED) {
+ task.tryStart(world, entity, time);
+ }
+ }
+ // Paper end - Perf: Remove streams from hot code
}
};
- public abstract <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time);
+ public abstract <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time); // Paper - Perf: Remove streams from hot code
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
index aa32804bc9affe9a615d3ffaa513f6f09aab3f32..c7f012674361a323c1efeca4660cd3f46d308ee1 100644
--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
@@ -59,8 +59,22 @@ public class GossipContainer {
return this.gossips.entrySet().stream().flatMap(entry -> entry.getValue().unpack(entry.getKey()));
}
+ // Paper start - Perf: Remove streams from hot code
+ private List<GossipContainer.GossipEntry> decompress() {
+ List<GossipContainer.GossipEntry> list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
+ for (Map.Entry<UUID, GossipContainer.EntityGossips> entry : this.gossips.entrySet()) {
+ for (GossipContainer.GossipEntry cur : entry.getValue().decompress(entry.getKey())) {
+ if (cur.weightedValue() != 0) {
+ list.add(cur);
+ }
+ }
+ }
+ return list;
+ }
+ // Paper end - Perf: Remove streams from hot code
+
private Collection<GossipContainer.GossipEntry> selectGossipsForTransfer(RandomSource random, int count) {
- List<GossipContainer.GossipEntry> list = this.unpack().toList();
+ List<GossipContainer.GossipEntry> list = this.decompress(); // Paper - Perf: Remove streams from hot code
if (list.isEmpty()) {
return Collections.emptyList();
} else {
@@ -145,7 +159,7 @@ public class GossipContainer {
public <T> T store(DynamicOps<T> ops) {
return GossipContainer.GossipEntry.LIST_CODEC
- .encodeStart(ops, this.unpack().toList())
+ .encodeStart(ops, this.decompress()) // Paper - Perf: Remove streams from hot code
.resultOrPartial(error -> LOGGER.warn("Failed to serialize gossips: {}", error))
.orElseGet(ops::emptyList);
}
@@ -172,12 +186,23 @@ public class GossipContainer {
final Object2IntMap<GossipType> entries = new Object2IntOpenHashMap<>();
public int weightedValue(Predicate<GossipType> gossipTypeFilter) {
- return this.entries
- .object2IntEntrySet()
- .stream()
- .filter(entry -> gossipTypeFilter.test(entry.getKey()))
- .mapToInt(entry -> entry.getIntValue() * entry.getKey().weight)
- .sum();
+ // Paper start - Perf: Remove streams from hot code
+ int weight = 0;
+ for (Object2IntMap.Entry<GossipType> entry : entries.object2IntEntrySet()) {
+ if (gossipTypeFilter.test(entry.getKey())) {
+ weight += entry.getIntValue() * entry.getKey().weight;
+ }
+ }
+ return weight;
+ }
+
+ public List<GossipContainer.GossipEntry> decompress(UUID uuid) {
+ List<GossipContainer.GossipEntry> list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
+ for (Object2IntMap.Entry<GossipType> entry : entries.object2IntEntrySet()) {
+ list.add(new GossipContainer.GossipEntry(uuid, entry.getKey(), entry.getIntValue()));
+ }
+ return list;
+ // Paper end - Perf: Remove streams from hot code
}
public Stream<GossipContainer.GossipEntry> unpack(UUID target) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
index 28fd073ddad358e087e8c78985a97cad21be81b7..a5bd308d1b3ea5db185c06a287167d1d8894a987 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
@@ -24,13 +24,17 @@ public class NearestItemSensor extends Sensor<Mob> {
@Override
protected void doTick(ServerLevel world, Mob entity) {
Brain<?> brain = entity.getBrain();
- List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> true);
+ List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(itemEntity.getItem())); // Paper - Perf: Move predicate into getEntities
list.sort(Comparator.comparingDouble(entity::distanceToSqr));
- Optional<ItemEntity> optional = list.stream()
- .filter(itemEntity -> entity.wantsToPickUp(itemEntity.getItem()))
- .filter(itemEntity -> itemEntity.closerThan(entity, 32.0))
- .filter(entity::hasLineOfSight)
- .findFirst();
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional);
+ // Paper start - Perf: remove streams from hot code
+ ItemEntity nearest = null;
+ for (ItemEntity entityItem : list) {
+ if (entity.hasLineOfSight(entityItem)) { // Paper - Perf: Move predicate into getEntities
+ nearest = entityItem;
+ break;
+ }
+ }
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest));
+ // Paper end - Perf: remove streams from hot code
}
}
diff --git a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
index 04552e2cf3de2dc41dfe7e7a2e88b200eaaf280b..ca93a97256350789ca56f910862c9d717ca7670b 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
@@ -35,9 +35,10 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
int j = pos.getMinBlockZ();
ObjectList<Beardifier.Rigid> objectList = new ObjectArrayList<>(10);
ObjectList<JigsawJunction> objectList2 = new ObjectArrayList<>(32);
- world.startsForStructure(pos, structure -> structure.terrainAdaptation() != TerrainAdjustment.NONE)
- .forEach(
- start -> {
+ // Paper start - Perf: Remove streams from hot code
+ for (net.minecraft.world.level.levelgen.structure.StructureStart start : world.startsForStructure(pos, (structure) -> {
+ return structure.terrainAdaptation() != TerrainAdjustment.NONE;
+ })) { // Paper end - Perf: Remove streams from hot code
TerrainAdjustment terrainAdjustment = start.getStructure().terrainAdaptation();
for (StructurePiece structurePiece : start.getPieces()) {
@@ -65,8 +66,7 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
}
}
}
- }
- );
+ } // Paper - Perf: Remove streams from hot code
return new Beardifier(objectList.iterator(), objectList2.iterator());
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Tue, 4 Aug 2020 22:24:15 +0200
Subject: [PATCH] Optimize Pathfinder - Remove Streams / Optimized collections
I utilized the IDE to convert streams to non streams code, so shouldn't
be any risk of behavior change. Only did minor optimization of the
generated code set to remove unnecessary things.
I expect us to just drop this patch on next major update and re-apply
it with the IDE again and re-apply the collections optimization.
Optimize collection by creating a list instead of a set of the key and value.
This lets us get faster foreach iteration, as well as avoids map lookups on
the values when needed.
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
index 8a483c8d2a54617d78af19de32fa0ded71b3223a..18bbb3f8f99849333ff4bc020c8ce758a69312a5 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -38,8 +38,12 @@ public class PathFinder {
if (node == null) {
return null;
} else {
- Map<Target, BlockPos> map = positions.stream()
- .collect(Collectors.toMap(pos -> this.nodeEvaluator.getTarget((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()), Function.identity()));
+ // Paper start - Perf: remove streams and optimize collection
+ List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
+ for (final BlockPos pos : positions) {
+ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos));
+ }
+ // Paper end - Perf: remove streams and optimize collection
Path path = this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier);
this.nodeEvaluator.done();
return path;
@@ -47,18 +51,19 @@ public class PathFinder {
}
@Nullable
- private Path findPath(ProfilerFiller profiler, Node startNode, Map<Target, BlockPos> positions, float followRange, int distance, float rangeMultiplier) {
+ // Paper start - Perf: remove streams and optimize collection
+ private Path findPath(ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
profiler.push("find_path");
profiler.markForCharting(MetricCategory.PATH_FINDING);
- Set<Target> set = positions.keySet();
+ // Set<Target> set = positions.keySet();
startNode.g = 0.0F;
- startNode.h = this.getBestH(startNode, set);
+ startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
startNode.f = startNode.h;
this.openSet.clear();
this.openSet.insert(startNode);
- Set<Node> set2 = ImmutableSet.of();
+ // Set<Node> set2 = ImmutableSet.of(); // Paper - unused - diff on change
int i = 0;
- Set<Target> set3 = Sets.newHashSetWithExpectedSize(set.size());
+ List<Map.Entry<Target, BlockPos>> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection
int j = (int)((float)this.maxVisitedNodes * rangeMultiplier);
while (!this.openSet.isEmpty()) {
@@ -69,14 +74,18 @@ public class PathFinder {
Node node = this.openSet.pop();
node.closed = true;
- for (Target target : set) {
+ // Paper start - optimize collection
+ for (int i1 = 0; i1 < positions.size(); i1++) {
+ final Map.Entry<Target, BlockPos> entry = positions.get(i1);
+ Target target = entry.getKey();
if (node.distanceManhattan(target) <= (float)distance) {
target.setReached();
- set3.add(target);
+ entryList.add(entry);
+ // Paper end - Perf: remove streams and optimize collection
}
}
- if (!set3.isEmpty()) {
+ if (!entryList.isEmpty()) { // Paper - Perf: remove streams and optimize collection; rename
break;
}
@@ -91,7 +100,7 @@ public class PathFinder {
if (node2.walkedDistance < followRange && (!node2.inOpenSet() || g < node2.g)) {
node2.cameFrom = node;
node2.g = g;
- node2.h = this.getBestH(node2, set) * 1.5F;
+ node2.h = this.getBestH(node2, positions) * 1.5F; // Paper - Perf: remove streams and optimize collection
if (node2.inOpenSet()) {
this.openSet.changeCost(node2, node2.g + node2.h);
} else {
@@ -103,23 +112,32 @@ public class PathFinder {
}
}
- Optional<Path> optional = !set3.isEmpty()
- ? set3.stream().map(node -> this.reconstructPath(node.getBestNode(), positions.get(node), true)).min(Comparator.comparingInt(Path::getNodeCount))
- : set.stream()
- .map(targetx -> this.reconstructPath(targetx.getBestNode(), positions.get(targetx), false))
- .min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount));
+ // Paper start - Perf: remove streams and optimize collection
+ Path best = null;
+ boolean entryListIsEmpty = entryList.isEmpty();
+ Comparator<Path> comparator = entryListIsEmpty ? Comparator.comparingInt(Path::getNodeCount)
+ : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount);
+ for (Map.Entry<Target, BlockPos> entry : entryListIsEmpty ? positions : entryList) {
+ Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty);
+ if (best == null || comparator.compare(path, best) < 0)
+ best = path;
+ }
profiler.pop();
- return optional.isEmpty() ? null : optional.get();
+ return best;
+ // Paper end - Perf: remove streams and optimize collection
}
protected float distance(Node a, Node b) {
return a.distanceTo(b);
}
- private float getBestH(Node node, Set<Target> targets) {
+ private float getBestH(Node node, List<Map.Entry<Target, BlockPos>> targets) { // Paper - Perf: remove streams and optimize collection; Set<Target> -> List<Map.Entry<Target, BlockPos>>
float f = Float.MAX_VALUE;
- for (Target target : targets) {
+ // Paper start - Perf: remove streams and optimize collection
+ for (int i = 0, targetsSize = targets.size(); i < targetsSize; i++) {
+ final Target target = targets.get(i).getKey();
+ // Paper end - Perf: remove streams and optimize collection
float g = node.distanceTo(target);
target.updateBest(g, node);
f = Math.min(g, f);

View file

@ -0,0 +1,68 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 2 Aug 2021 10:10:40 +0200
Subject: [PATCH] Improve boat collision performance
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
index 2cd0a4dc4f0baa08bd7f5a053303bb63733f0bab..0bd367235f80c1f0d319a6aa5130d82ad82d895c 100644
--- a/src/main/java/net/minecraft/Util.java
+++ b/src/main/java/net/minecraft/Util.java
@@ -125,6 +125,7 @@ public class Util {
.filter(fileSystemProvider -> fileSystemProvider.getScheme().equalsIgnoreCase("jar"))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No jar file system provider found"));
+ public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Improve boat collision performance
private static Consumer<String> thePauser = message -> {
};
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 5854e58a014b5581fa065d1db256ffc0aa947ce2..75e01c1e01e0782fc8af48777bbe4716d37aeafa 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1459,7 +1459,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (!source.is(DamageTypeTags.IS_PROJECTILE)) {
Entity entity = source.getDirectEntity();
- if (entity instanceof LivingEntity) {
+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance
LivingEntity entityliving = (LivingEntity) entity;
this.blockUsingShield(entityliving);
@@ -1553,11 +1553,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
if (entity1 != null && !source.is(DamageTypeTags.NO_KNOCKBACK)) {
- double d0 = entity1.getX() - this.getX();
+ final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper - Improve boat collision performance
+ double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper - Improve boat collision performance
double d1;
- for (d1 = entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) {
+ for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper - Improve boat collision performance
d0 = (Math.random() - Math.random()) * 0.01D;
}
@@ -2334,7 +2335,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
Entity entity = damagesource.getDirectEntity();
- if (entity instanceof LivingEntity) {
+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance
this.blockUsingShield((LivingEntity) entity);
}
}
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
index 549202706396fee91c23f6e34f7faa7672e4b03f..b068cff9b5aa457d65b679529956e8210296d799 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
@@ -686,7 +686,7 @@ public class Boat extends VehicleEntity implements VariantHolder<Boat.Type> {
this.invFriction = 0.05F;
if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) {
this.waterLevel = this.getY(1.0D);
- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ());
+ this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper - Improve boat collision performance
this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
this.lastYd = 0.0D;
this.status = Boat.Status.IN_WATER;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,343 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 11 Mar 2021 20:05:44 -0800
Subject: [PATCH] Custom table implementation for blockstate state lookups
Testing some redstone intensive machines showed to bring about a 10%
improvement.
diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..57d0cd3ad6f972e986c72a57f1a6e36003f190c2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
@@ -0,0 +1,160 @@
+package io.papermc.paper.util.table;
+
+import com.google.common.collect.Table;
+import net.minecraft.world.level.block.state.StateHolder;
+import net.minecraft.world.level.block.state.properties.Property;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public final class ZeroCollidingReferenceStateTable {
+
+ // upper 32 bits: starting index
+ // lower 32 bits: bitset for contained ids
+ protected final long[] this_index_table;
+ protected final Comparable<?>[] this_table;
+ protected final StateHolder<?, ?> this_state;
+
+ protected long[] index_table;
+ protected StateHolder<?, ?>[][] value_table;
+
+ public ZeroCollidingReferenceStateTable(final StateHolder<?, ?> state, final Map<Property<?>, Comparable<?>> this_map) {
+ this.this_state = state;
+ this.this_index_table = this.create_table(this_map.keySet());
+
+ int max_id = -1;
+ for (final Property<?> property : this_map.keySet()) {
+ final int id = lookup_vindex(property, this.this_index_table);
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ this.this_table = new Comparable[max_id + 1];
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue();
+ }
+ }
+
+ public void loadInTable(final Table<Property<?>, Comparable<?>, StateHolder<?, ?>> table,
+ final Map<Property<?>, Comparable<?>> this_map) {
+ final Set<Property<?>> combined = new HashSet<>(table.rowKeySet());
+ combined.addAll(this_map.keySet());
+
+ this.index_table = this.create_table(combined);
+
+ int max_id = -1;
+ for (final Property<?> property : combined) {
+ final int id = lookup_vindex(property, this.index_table);
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ this.value_table = new StateHolder[max_id + 1][];
+
+ final Map<Property<?>, Map<Comparable<?>, StateHolder<?, ?>>> map = table.rowMap();
+ for (final Property<?> property : map.keySet()) {
+ final Map<Comparable<?>, StateHolder<?, ?>> propertyMap = map.get(property);
+
+ final int id = lookup_vindex(property, this.index_table);
+ final StateHolder<?, ?>[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()];
+
+ for (final Map.Entry<Comparable<?>, StateHolder<?, ?>> entry : propertyMap.entrySet()) {
+ if (entry.getValue() == null) {
+ // TODO what
+ continue;
+ }
+
+ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue();
+ }
+ }
+
+
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
+ final Property<?> property = entry.getKey();
+ final int index = lookup_vindex(property, this.index_table);
+
+ if (this.value_table[index] == null) {
+ this.value_table[index] = new StateHolder[property.getPossibleValues().size()];
+ }
+
+ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state;
+ }
+ }
+
+
+ protected long[] create_table(final Collection<Property<?>> collection) {
+ int max_id = -1;
+ for (final Property<?> property : collection) {
+ final int id = property.getId();
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32)
+
+ for (final Property<?> property : collection) {
+ final int id = property.getId();
+
+ ret[id >>> 5] |= (1L << (id & 31));
+ }
+
+ int total = 0;
+ for (int i = 1, len = ret.length; i < len; ++i) {
+ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32;
+ }
+
+ return ret;
+ }
+
+ public Comparable<?> get(final Property<?> state) {
+ final Comparable<?>[] table = this.this_table;
+ final int index = lookup_vindex(state, this.this_index_table);
+
+ if (index < 0 || index >= table.length) {
+ return null;
+ }
+ return table[index];
+ }
+
+ public StateHolder<?, ?> get(final Property<?> property, final Comparable<?> with) {
+ final int withId = ((Property)property).getIdFor(with);
+ if (withId < 0) {
+ return null;
+ }
+
+ final int index = lookup_vindex(property, this.index_table);
+ final StateHolder<?, ?>[][] table = this.value_table;
+ if (index < 0 || index >= table.length) {
+ return null;
+ }
+
+ final StateHolder<?, ?>[] values = table[index];
+
+ if (withId >= values.length) {
+ return null;
+ }
+
+ return values[withId];
+ }
+
+ protected static int lookup_vindex(final Property<?> property, final long[] index_table) {
+ final int id = property.getId();
+ final long bitset_mask = (1L << (id & 31));
+ final long lower_mask = bitset_mask - 1;
+ final int index = id >>> 5;
+ if (index >= index_table.length) {
+ return -1;
+ }
+ final long index_value = index_table[index];
+ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain
+
+ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id
+ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0,
+ // otherwise it comes out as -1.
+ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check);
+ }
+}
diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
index daedcfd867ed6171fb61bdcbded417a11c8a5b0f..223ab2d0256fa6df8064f16bc5f8fbe87a1422e4 100644
--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
@@ -39,11 +39,13 @@ public abstract class StateHolder<O, S> {
private final Reference2ObjectArrayMap<Property<?>, Comparable<?>> values;
private Table<Property<?>, Comparable<?>, S> neighbours;
protected final MapCodec<S> propertiesCodec;
+ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup
protected StateHolder(O owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<S> codec) {
this.owner = owner;
this.values = propertyMap;
this.propertiesCodec = codec;
+ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup
}
public <T extends Comparable<T>> S cycle(Property<T> property) {
@@ -84,11 +86,11 @@ public abstract class StateHolder<O, S> {
}
public <T extends Comparable<T>> boolean hasProperty(Property<T> property) {
- return this.values.containsKey(property);
+ return this.optimisedTable.get(property) != null; // Paper - optimise state lookup
}
public <T extends Comparable<T>> T getValue(Property<T> property) {
- Comparable<?> comparable = this.values.get(property);
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
if (comparable == null) {
throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
} else {
@@ -97,24 +99,18 @@ public abstract class StateHolder<O, S> {
}
public <T extends Comparable<T>> Optional<T> getOptionalValue(Property<T> property) {
- Comparable<?> comparable = this.values.get(property);
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable));
}
public <T extends Comparable<T>, V extends T> S setValue(Property<T> property, V value) {
- Comparable<?> comparable = this.values.get(property);
- if (comparable == null) {
- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner);
- } else if (comparable.equals(value)) {
- return (S)this;
- } else {
- S object = this.neighbours.get(property, value);
- if (object == null) {
- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
- } else {
- return object;
- }
+ // Paper start - optimise state lookup
+ final S ret = (S)this.optimisedTable.get(property, value);
+ if (ret == null) {
+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
}
+ return ret;
+ // Paper end - optimise state lookup
}
public <T extends Comparable<T>, V extends T> S trySetValue(Property<T> property, V value) {
@@ -147,7 +143,7 @@ public abstract class StateHolder<O, S> {
}
}
- this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table));
+ this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
index b63116b333b6e06494091a82588acfb639bddb71..ff5fd91257c4554c523682009efe1db83f53fd5b 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
@@ -7,6 +7,13 @@ import java.util.Optional;
public class BooleanProperty extends Property<Boolean> {
private final ImmutableSet<Boolean> values = ImmutableSet.of(true, false);
+ // Paper start - optimise iblockdata state lookup
+ @Override
+ public final int getIdFor(final Boolean value) {
+ return value.booleanValue() ? 1 : 0;
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected BooleanProperty(String name) {
super(name, Boolean.class);
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
index 3097298fe356df98967cf4bdeaaede69dfe8a441..498c5abe0a9d024d77029719c621c1c8485791f3 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
@@ -15,6 +15,15 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
private final ImmutableSet<T> values;
private final Map<String, T> names = Maps.newHashMap();
+ // Paper start - optimise iblockdata state lookup
+ private int[] idLookupTable;
+
+ @Override
+ public final int getIdFor(final T value) {
+ return this.idLookupTable[value.ordinal()];
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected EnumProperty(String name, Class<T> type, Collection<T> values) {
super(name, type);
this.values = ImmutableSet.copyOf(values);
@@ -27,6 +36,14 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
this.names.put(string, enum_);
}
+ // Paper start - optimise BlockState lookup
+ int id = 0;
+ this.idLookupTable = new int[type.getEnumConstants().length];
+ java.util.Arrays.fill(this.idLookupTable, -1);
+ for (final T value : this.getPossibleValues()) {
+ this.idLookupTable[value.ordinal()] = id++;
+ }
+ // Paper end - optimise BlockState lookup
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
index 3a850321a4bcc68058483b5fd53e829c425a68af..977504f2641d0133a572b0d5de85d058609343bb 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
@@ -11,6 +11,16 @@ public class IntegerProperty extends Property<Integer> {
public final int min;
public final int max;
+ // Paper start - optimise iblockdata state lookup
+ @Override
+ public final int getIdFor(final Integer value) {
+ final int val = value.intValue();
+ final int ret = val - this.min;
+
+ return ret | ((this.max - ret) >> 31);
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected IntegerProperty(String name, int min, int max) {
super(name, Integer.class);
if (min < 0) {
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
index 9055f15af0cae55effa6942913a9d7edf3857e07..b9493e3762410aca8e683c32b5aef187c0bee082 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
@@ -24,6 +24,17 @@ public abstract class Property<T extends Comparable<T>> {
);
private final Codec<Property.Value<T>> valueCodec = this.codec.xmap(this::value, Property.Value::value);
+ // Paper start - optimise iblockdata state lookup
+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
+ private final int id = ID_GENERATOR.getAndIncrement();
+
+ public final int getId() {
+ return this.id;
+ }
+
+ public abstract int getIdFor(final T value);
+ // Paper end - optimise state lookup
+
protected Property(String name, Class<T> type) {
this.clazz = type;
this.name = name;

View file

@ -0,0 +1,176 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 6 Apr 2020 04:20:44 -0700
Subject: [PATCH] Execute chunk tasks mid-tick
This will help the server load chunks if tick times are high.
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 6b3cde6d4d1e63bec01f502f2027ee9fddac08aa..46449728f69ee7d4f78470f8da23c055acd53a3b 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -48,6 +48,8 @@ public final class MinecraftTimings {
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
+
private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
private MinecraftTimings() {}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index affc74c4650ce03aeb723619b63035d539958a8e..88549de2768149885c4eabbaaa71a38341453aed 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1414,8 +1414,79 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return flag;
}
+ // Paper start - execute chunk tasks mid tick
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
+
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
+
+ private static long lastMidTickExecute;
+ private static long lastMidTickExecuteFailure;
+
+ private boolean tickMidTickTasks() {
+ // give all worlds a fair chance at by targetting them all.
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
+ boolean executed = false;
+ for (ServerLevel world : this.getAllLevels()) {
+ long currTime = System.nanoTime();
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ continue;
+ }
+ if (!world.getChunkSource().pollTask()) {
+ // we need to back off if this fails
+ world.lastMidTickExecuteFailure = currTime;
+ } else {
+ executed = true;
+ }
+ }
+
+ return executed;
+ }
+
+ public final void executeMidTickTasks() {
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
+ long startTime = System.nanoTime();
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
+ // so, backoff to prevent this
+ return;
+ }
+
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
+ try {
+ for (;;) {
+ boolean moreTasks = this.tickMidTickTasks();
+ long currTime = System.nanoTime();
+ long diff = currTime - startTime;
+
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
+ if (!moreTasks) {
+ lastMidTickExecuteFailure = currTime;
+ }
+
+ // note: negative values reduce the time
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
+ // make sure something like a GC or dumb plugin doesn't screw us over...
+ overuse = 10L * 1000L * 1000L; // 10ms
+ }
+
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
+
+ lastMidTickExecute = currTime + extraSleep;
+ return;
+ }
+ }
+ } finally {
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
+ }
+ }
+ // Paper end - execute chunk tasks mid tick
+
private boolean pollTaskInternal() {
if (super.pollTask()) {
+ this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 0b7a38b9e92b19345a34c6226413a9b133264077..18640c8681f6a3b2276123d19e3e8f0a8c630b41 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -566,6 +566,7 @@ public class ServerChunkCache extends ChunkSource {
boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
Iterator iterator1 = list.iterator();
+ int chunksTicked = 0; // Paper
while (iterator1.hasNext()) {
ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
LevelChunk chunk1 = chunkproviderserver_a.chunk;
@@ -579,6 +580,7 @@ public class ServerChunkCache extends ChunkSource {
if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
this.level.tickChunk(chunk1, l);
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
}
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index aba5f694b25507c9ab2e214bc1f25e0a895e8a95..d917407dbab842c6aec5f5a37f29dc9dd6d86407 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -221,6 +221,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
private final StructureCheck structureCheck;
private final boolean tickTime;
private final RandomSequences randomSequences;
+ public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
// CraftBukkit start
public final LevelStorageSource.LevelStorageAccess convertable;
@@ -1211,6 +1212,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (fluid1.is(fluid)) {
fluid1.tick(this, pos);
}
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
}
@@ -1220,6 +1222,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (iblockdata.is(block)) {
iblockdata.tick(this, pos, this.random);
}
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 232ac315fa1ed0e6e9ff4882d3378745804e4e1c..e982de0ec4e45219962ea775f355a5740b3906af 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -916,6 +916,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Spigot end
} else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
tickingblockentity.tick();
+ // Paper start - execute chunk tasks during tick
+ if ((this.tileTickPosition & 7) == 0) {
+ MinecraftServer.getServer().executeMidTickTasks();
+ }
+ // Paper end - execute chunk tasks during tick
}
}
this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
@@ -930,6 +935,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
try {
tickConsumer.accept(entity);
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent block entity and entity crashes

View file

@ -0,0 +1,456 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 20 Jun 2021 16:19:26 -0700
Subject: [PATCH] Optimise random block ticking
Massive performance improvement for random block ticking.
The performance increase comes from the fact that the vast
majority of attempted block ticks (~95% in my testing) fail
because the randomly selected block is not tickable.
Now only tickable blocks are targeted, however this means that
the maximum number of block ticks occurs per chunk. However,
not all chunks are going to be targeted. The percent chance
of a chunk being targeted is based on how many tickable blocks
are in the chunk.
This means that while block ticks are spread out less, the
total number of blocks ticked per world tick remains the same.
Therefore, the chance of a random tickable block being ticked
remains the same.
diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
@@ -0,0 +1,65 @@
+package io.papermc.paper.util.math;
+
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.levelgen.LegacyRandomSource;
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class ThreadUnsafeRandom extends LegacyRandomSource {
+
+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them.
+ private static final long multiplier = 0x5DEECE66DL;
+ private static final long addend = 0xBL;
+ private static final long mask = (1L << 48) - 1;
+
+ private static long initialScramble(long seed) {
+ return (seed ^ multiplier) & mask;
+ }
+
+ private long seed;
+
+ public ThreadUnsafeRandom(long seed) {
+ super(seed);
+ }
+
+ @Override
+ public RandomSource fork() {
+ return new ThreadUnsafeRandom(this.nextLong());
+ }
+
+ @Override
+ public PositionalRandomFactory forkPositional() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSeed(long seed) {
+ // note: called by Random constructor
+ this.seed = initialScramble(seed);
+ }
+
+ @Override
+ public int next(int bits) {
+ // avoid the expensive CAS logic used by superclass
+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits));
+ }
+
+ // Taken from
+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
+ // Original license is public domain
+ public static int fastRandomBounded(final long randomInteger, final long limit) {
+ // randomInteger must be [0, pow(2, 32))
+ // limit must be [0, pow(2, 32))
+ return (int)((randomInteger * limit) >>> 32);
+ }
+
+ @Override
+ public int nextInt(int bound) {
+ // yes this breaks random's spec
+ // however there's nothing that uses this class that relies on it
+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound);
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index d917407dbab842c6aec5f5a37f29dc9dd6d86407..16d24e70072e3846b3c35d331c3f474238a8def2 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -861,6 +861,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
entityplayer.stopSleepInBed(false, false);
});
}
+ // Paper start - optimise random block ticking
+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong());
+ // Paper end
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
ChunkPos chunkcoordintpair = chunk.getPos();
@@ -870,8 +874,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
ProfilerFiller gameprofilerfiller = this.getProfiler();
gameprofilerfiller.push("thunder");
+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
+
if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
if (this.isRainingAt(blockposition)) {
DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
@@ -903,7 +909,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
for (int l = 0; l < randomTickSpeed; ++l) {
if (this.random.nextInt(48) == 0) {
- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15));
+ // Paper start
+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
+ this.tickPrecipitation(blockposition, chunk);
+ // Paper end
}
}
} // Paper - Option to disable ice and snow
@@ -911,36 +920,37 @@ public class ServerLevel extends Level implements WorldGenLevel {
gameprofilerfiller.popPush("tickBlocks");
timings.chunkTicksBlocks.startTiming(); // Paper
if (randomTickSpeed > 0) {
- LevelChunkSection[] achunksection = chunk.getSections();
-
- for (int i1 = 0; i1 < achunksection.length; ++i1) {
- LevelChunkSection chunksection = achunksection[i1];
-
- if (chunksection.isRandomlyTicking()) {
- int j1 = chunk.getSectionYFromSectionIndex(i1);
- int k1 = SectionPos.sectionToBlockCoord(j1);
-
- for (int l1 = 0; l1 < randomTickSpeed; ++l1) {
- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15);
-
- gameprofilerfiller.push("randomTick");
- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k);
-
- if (iblockdata.isRandomlyTicking()) {
- iblockdata.randomTick(this, blockposition1, this.random);
- }
+ // Paper start - optimize random block ticking
+ LevelChunkSection[] sections = chunk.getSections();
+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this);
+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
+ LevelChunkSection section = sections[sectionIndex];
+ if (section == null || section.tickingList.size() == 0) continue;
+
+ int yPos = (sectionIndex + minSection) << 4;
+ for (int a = 0; a < randomTickSpeed; ++a) {
+ int tickingBlocks = section.tickingList.size();
+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
+ if (index >= tickingBlocks) {
+ continue;
+ }
- FluidState fluid = iblockdata.getFluidState();
+ long raw = section.tickingList.getRaw(index);
+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw);
+ int randomX = location & 15;
+ int randomY = ((location >>> (4 + 4)) & 255) | yPos;
+ int randomZ = (location >>> 4) & 15;
- if (fluid.isRandomlyTicking()) {
- fluid.randomTick(this, blockposition1, this.random);
- }
+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ);
+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
- gameprofilerfiller.pop();
- }
+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom);
}
+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock).
+ // TODO CHECK ON UPDATE (ping the Canadian)
}
}
+ // Paper end - optimise random block ticking
timings.chunkTicksBlocks.stopTiming(); // Paper
gameprofilerfiller.pop();
@@ -948,17 +958,25 @@ public class ServerLevel extends Level implements WorldGenLevel {
@VisibleForTesting
public void tickPrecipitation(BlockPos pos) {
- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
- BlockPos blockposition2 = blockposition1.below();
+ // Paper start - optimise chunk ticking
+ tickPrecipitation(pos.mutable(), this.getChunkAt(pos));
+ }
+ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) {
+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1;
+ int downY = normalY - 1;
+ blockposition1.setY(normalY);
+ // Paper end - optimise chunk ticking
Biome biomebase = (Biome) this.getBiome(blockposition1).value();
- if (biomebase.shouldFreeze(this, blockposition2)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ blockposition1.setY(downY);
+ if (biomebase.shouldFreeze(this, blockposition1)) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
}
if (this.isRaining()) {
int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
+ blockposition1.setY(normalY); // Paper - optimise chunk ticking
if (i > 0 && biomebase.shouldSnow(this, blockposition1)) {
BlockState iblockdata = this.getBlockState(blockposition1);
@@ -976,12 +994,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
}
- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2);
+ blockposition1.setY(downY); // Paper - optimise chunk ticking
+ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking
if (biomebase_precipitation != Biome.Precipitation.NONE) {
- BlockState iblockdata2 = this.getBlockState(blockposition2);
+ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking
- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation);
+ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking
}
}
diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
index 68648c5a5e3ff079f832092af0f2f801c42d1ede..8bafd5fd7499ba4a04bf706cfd1e156073716e21 100644
--- a/src/main/java/net/minecraft/util/BitStorage.java
+++ b/src/main/java/net/minecraft/util/BitStorage.java
@@ -20,4 +20,15 @@ public interface BitStorage {
void unpack(int[] out);
BitStorage copy();
+
+ // Paper start
+ void forEach(DataBitConsumer consumer);
+
+ @FunctionalInterface
+ interface DataBitConsumer {
+
+ void accept(int location, int data);
+
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java
index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..8d7d763bf51cac556057645e6169c9447993189b 100644
--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
@@ -315,6 +315,28 @@ public class SimpleBitStorage implements BitStorage {
return this.bits;
}
+ // Paper start
+ @Override
+ public final void forEach(DataBitConsumer consumer) {
+ int i = 0;
+ long[] along = this.data;
+ int j = along.length;
+
+ for (int k = 0; k < j; ++k) {
+ long l = along[k];
+
+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) {
+ consumer.accept(i, (int) (l & this.mask));
+ l >>= this.bits;
+ ++i;
+ if (i >= this.size) {
+ return;
+ }
+ }
+ }
+ }
+ // Paper end
+
@Override
public void getAll(IntConsumer action) {
int i = 0;
diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java
index 50040c497a819cd1229042ab3cb057d34a32cacc..01f5b946fabbe34f31110e75973dab9f39897346 100644
--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java
+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java
@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage {
return 0;
}
+ // Paper start
+ @Override
+ public void forEach(DataBitConsumer consumer) {
+ for(int i = 0; i < this.size; ++i) {
+ consumer.accept(i, 0);
+ }
+ }
+ // Paper end
+
@Override
public void getAll(IntConsumer action) {
for (int i = 0; i < this.size; i++) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
index ffffa53fcaa6ec8681b283fa20bb5a3320ad9c11..30b87b5cb18c25cdd04eab64cfbe5acd6c1b6d84 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -88,7 +88,7 @@ public class Turtle extends Animal {
}
public void setHomePos(BlockPos pos) {
- this.entityData.set(Turtle.HOME_POS, pos);
+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
}
public BlockPos getHomePos() {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index e982de0ec4e45219962ea775f355a5740b3906af..5b3143723e1637c2ab44363b208cd26b659d70cc 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1388,10 +1388,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public abstract RecipeManager getRecipeManager();
public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
+ // Paper start - allow use of mutable pos
+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos();
+ this.getRandomBlockPosition(x, y, z, l, ret);
+ return ret.immutable();
+ }
+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) {
+ // Paper end
this.randValue = this.randValue * 3 + 1013904223;
int i1 = this.randValue >> 2;
- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call
+ return out; // Paper
}
public boolean noSave() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index f2e11bff414b521295bde721e01ae2166a6a3fd6..8cd6c1d838e0332125fde3fc36475034aa4effa0 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -25,6 +25,7 @@ public class LevelChunkSection {
public final PalettedContainer<BlockState> states;
// CraftBukkit start - read/write
private PalettedContainer<Holder<Biome>> biomes;
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
// CraftBukkit end
@@ -77,6 +78,9 @@ public class LevelChunkSection {
--this.nonEmptyBlockCount;
if (iblockdata1.isRandomlyTicking()) {
--this.tickingBlockCount;
+ // Paper start
+ this.tickingList.remove(x, y, z);
+ // Paper end
}
}
@@ -88,6 +92,9 @@ public class LevelChunkSection {
++this.nonEmptyBlockCount;
if (state.isRandomlyTicking()) {
++this.tickingBlockCount;
+ // Paper start
+ this.tickingList.add(x, y, z, state);
+ // Paper end
}
}
@@ -115,40 +122,34 @@ public class LevelChunkSection {
}
public void recalcBlockCounts() {
- class a implements PalettedContainer.CountConsumer<BlockState> {
-
- public int nonEmptyBlockCount;
- public int tickingBlockCount;
- public int tickingFluidCount;
-
- a(final LevelChunkSection chunksection) {}
-
- public void accept(BlockState iblockdata, int i) {
+ // Paper start - unfuck this
+ this.tickingList.clear();
+ this.nonEmptyBlockCount = 0;
+ this.tickingBlockCount = 0;
+ this.tickingFluidCount = 0;
+ // Don't run this on clearly empty sections
+ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) {
+ this.states.forEachLocation((BlockState iblockdata, int i) -> {
FluidState fluid = iblockdata.getFluidState();
if (!iblockdata.isAir()) {
- this.nonEmptyBlockCount += i;
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
if (iblockdata.isRandomlyTicking()) {
- this.tickingBlockCount += i;
+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
+ this.tickingList.add(i, iblockdata);
}
}
if (!fluid.isEmpty()) {
- this.nonEmptyBlockCount += i;
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
if (fluid.isRandomlyTicking()) {
- this.tickingFluidCount += i;
+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
}
}
- }
+ });
}
-
- a a0 = new a(this);
-
- this.states.count(a0);
- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
- this.tickingBlockCount = (short) a0.tickingBlockCount;
- this.tickingFluidCount = (short) a0.tickingFluidCount;
+ // Paper end - unfuck this
}
public PalettedContainer<BlockState> getStates() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index 926c81a25180d508d662eb3fa35f771636164694..81368bf186365878db2e1ed305bb7bf36c26f61f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -381,6 +381,14 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
}
}
+ // Paper start
+ public void forEachLocation(PalettedContainer.CountConsumer<T> consumer) {
+ this.data.storage.forEach((int location, int data) -> {
+ consumer.accept(this.data.palette.valueFor(data), location);
+ });
+ }
+ // Paper end
+
@FunctionalInterface
public interface CountConsumer<T> {
void accept(T object, int count);

View file

@ -0,0 +1,779 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 2 Feb 2020 02:25:10 -0800
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
Instead of trying to relocate the chunk, which is seems to never
be the correct choice, so we end up duplicating or swapping chunks,
we instead drop the current regionfile header and recalculate -
hoping that at least then we don't swap chunks, and maybe recover
them all.
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index e64fe79b231987e240d7482c0d6fa3c89dc97462..db932f8e9d48e1e47a89526d1d76947e91b55eea 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -72,6 +72,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks;
import org.slf4j.Logger;
public class ChunkSerializer {
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ // TODO: Check on update
+ public static long getLastWorldSaveTime(CompoundTag chunkData) {
+ final int dataVersion = ChunkStorage.getVersion(chunkData);
+ if (dataVersion < 2842) { // Level tag is removed after this version
+ final CompoundTag levelData = chunkData.getCompound("Level");
+ return levelData.getLong("LastUpdate");
+ } else {
+ return chunkData.getLong("LastUpdate");
+ }
+ }
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
private static final Logger LOGGER = LogUtils.getLogger();
@@ -449,7 +461,7 @@ public class ChunkSerializer {
nbttagcompound.putInt("xPos", chunkcoordintpair.x);
nbttagcompound.putInt("yPos", chunk.getMinSection());
nbttagcompound.putInt("zPos", chunkcoordintpair.z);
- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change
nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
index 554dede2ad0e45d3ee4ccc5510b7644f2e9e4250..7801fac96d728f951989fca36f6a4890a0638c36 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -41,7 +41,7 @@ public class ChunkStorage implements AutoCloseable {
public ChunkStorage(RegionStorageInfo storageKey, Path directory, DataFixer dataFixer, boolean dsync) {
this.fixerUpper = dataFixer;
- this.regionFileCache = new RegionFileStorage(storageKey, directory, dsync); // Paper - rewrite chunk system; async chunk IO
+ this.regionFileCache = new RegionFileStorage(storageKey, directory, dsync, true); // Paper - rewrite chunk system; async chunk IO & Attempt to recalculate regionfile header if it is corrupt
}
public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
index a23dc2f8f4475de1ee35bf18a7a8a53233ccac12..226af44fd469053451a0403a95ffb446face9530 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
@@ -9,6 +9,27 @@ import java.util.BitSet;
public class RegionBitmap {
private final BitSet used = new BitSet();
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ public final void copyFrom(RegionBitmap other) {
+ BitSet thisBitset = this.used;
+ BitSet otherBitset = other.used;
+
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
+ thisBitset.set(i, otherBitset.get(i));
+ }
+ }
+
+ public final boolean tryAllocate(int from, int length) {
+ BitSet bitset = this.used;
+ int firstSet = bitset.nextSetBit(from);
+ if (firstSet > 0 && firstSet < (from + length)) {
+ return false;
+ }
+ bitset.set(from, from + length);
+ return true;
+ }
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
+
public void force(int start, int size) {
this.used.set(start, start + size);
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index 36cdf165334c4d7c1ca28ba42b1006b163e45a95..6e8c488ed05495237e06e44005d3656f16573e58 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -53,6 +53,356 @@ public class RegionFile implements AutoCloseable {
protected final RegionBitmap usedSectors;
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper
public final Path regionFile; // Paper
+
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ private static long roundToSectors(long bytes) {
+ long sectors = bytes >>> 12; // 4096 = 2^12
+ long remainingBytes = bytes & 4095;
+ long sign = -remainingBytes; // sign is 1 if nonzero
+ return sectors + (sign >>> 63);
+ }
+
+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag();
+
+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
+ try {
+ if (chunkDataLength < 0) {
+ return null;
+ }
+
+ long offset = sector * 4096L + 4L; // offset for chunk data
+
+ if ((offset + chunkDataLength) > fileLength) {
+ return null;
+ }
+
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
+ return null;
+ }
+
+ ((java.nio.Buffer)chunkData).flip();
+
+ byte compressionType = chunkData.get();
+ if (compressionType < 0) { // compressionType & 128 != 0
+ // oversized chunk
+ return OVERSIZED_COMPOUND;
+ }
+
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
+ if (compression == null) {
+ return null;
+ }
+
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
+
+ return NbtIo.read(new DataInputStream(input));
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private int getLength(long sector) throws IOException {
+ ByteBuffer length = ByteBuffer.allocate(4);
+ if (4 != this.file.read(length, sector * 4096L)) {
+ return -1;
+ }
+
+ return length.getInt(0);
+ }
+
+ private void backupRegionFile() {
+ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup");
+ this.backupRegionFile(backup);
+ }
+
+ private void backupRegionFile(Path to) {
+ try {
+ this.file.force(true);
+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath());
+ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
+ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex);
+ }
+ }
+
+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) {
+ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31);
+ }
+
+ // note: only call for CHUNK regionfiles
+ boolean recalculateHeader() throws IOException {
+ if (!this.canRecalcHeader) {
+ return false;
+ }
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile);
+ if (ourLowerLeftPosition == null) {
+ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header");
+ return false;
+ }
+ synchronized (this) {
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable());
+
+ // try to backup file so maybe it could be sent to us for further investigation
+
+ this.backupRegionFile();
+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
+ boolean[] hasAikarOversized = new boolean[32 * 32];
+
+ long fileLength = this.file.size();
+ long totalSectors = roundToSectors(fileLength);
+
+ // search the regionfile from start to finish for the most up-to-date chunk data
+
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
+ int chunkDataLength = this.getLength(i);
+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
+ continue;
+ }
+
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound);
+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) {
+ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")");
+ continue;
+ }
+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5);
+
+ CompoundTag otherCompound = compounds[location];
+
+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) {
+ continue; // don't overwrite newer data.
+ }
+
+ // aikar oversized?
+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z);
+ boolean isAikarOversized = false;
+ if (Files.exists(aikarOversizedFile)) {
+ try {
+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) {
+ // best we got for an id. hope it's good enough
+ isAikarOversized = true;
+ }
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex);
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
+ }
+ }
+
+ hasAikarOversized[location] = isAikarOversized;
+ compounds[location] = compound;
+ rawLengths[location] = chunkDataLength + 4;
+ sectorOffsets[location] = (int)i;
+
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
+ i += chunkSectorLength;
+ --i; // gets incremented next iteration
+ }
+
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
+ // local data compound
+
+ java.nio.file.Path containingFolder = this.externalFileDir;
+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new);
+ boolean[] oversized = new boolean[32 * 32];
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
+
+ if (regionFiles != null) {
+ int lowerXBound = ourLowerLeftPosition.x; // inclusive
+ int lowerZBound = ourLowerLeftPosition.z; // inclusive
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
+
+ // read mojang oversized data
+ for (Path regionFile : regionFiles) {
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
+ if (oversizedCoords == null) {
+ continue;
+ }
+
+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) {
+ continue; // not in our regionfile
+ }
+
+ // ensure oversized data is valid & is newer than data in the regionfile
+
+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5);
+
+ byte[] chunkData;
+ try {
+ chunkData = Files.readAllBytes(regionFile);
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex);
+ continue;
+ }
+
+ CompoundTag compound = null;
+
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
+ RegionFileVersion compression = null;
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
+ try {
+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
+ compound = NbtIo.read((java.io.DataInput)in);
+ compression = compressionType;
+ break; // reaches here iff readNBT does not throw
+ } catch (Exception ex) {
+ continue;
+ }
+ }
+
+ if (compound == null) {
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost");
+ continue;
+ }
+
+ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) {
+ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords);
+ continue;
+ }
+
+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) {
+ oversized[location] = true;
+ oversizedCompressionTypes[location] = compression;
+ }
+ }
+ }
+
+ // now we need to calculate a new offset header
+
+ int[] calculatedOffsets = new int[32 * 32];
+ RegionBitmap newSectorAllocations = new RegionBitmap();
+ newSectorAllocations.force(0, 2); // make space for header
+
+ // allocate sectors for normal chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (oversized[location]) {
+ continue;
+ }
+
+ int rawLength = rawLengths[location]; // bytes
+ int sectorOffset = sectorOffsets[location]; // sectors
+ int sectorLength = (int)roundToSectors(rawLength);
+
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } else {
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated");
+ }
+ }
+ }
+
+ // allocate sectors for oversized chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (!oversized[location]) {
+ continue;
+ }
+
+ int sectorOffset = newSectorAllocations.allocate(1);
+ int sectorLength = 1;
+
+ try {
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
+ // only allocate in the new offsets if the write succeeds
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } catch (IOException ex) {
+ newSectorAllocations.free(sectorOffset, sectorLength);
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated");
+ }
+ }
+ }
+
+ // rewrite aikar oversized data
+
+ this.oversizedCount = 0;
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
+
+ this.oversizedCount += isAikarOversized;
+ this.oversized[location] = (byte)isAikarOversized;
+ }
+ }
+
+ if (this.oversizedCount > 0) {
+ try {
+ this.writeOversizedMeta();
+ } catch (Exception ex) {
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex);
+ Files.deleteIfExists(this.getOversizedMetaFile());
+ }
+ } else {
+ Files.deleteIfExists(this.getOversizedMetaFile());
+ }
+
+ this.usedSectors.copyFrom(newSectorAllocations);
+
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
+
+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath());
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ int oldOffset = this.offsets.get(location);
+ int newOffset = calculatedOffsets[location];
+
+ if (oldOffset == newOffset) {
+ continue;
+ }
+
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
+
+ if (oldOffset == 0) {
+ // found lost data
+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath());
+ } else if (newOffset == 0) {
+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated");
+ } else {
+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath());
+ }
+ }
+ }
+
+ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath());
+
+ // simply destroy the timestamp header, it's not used
+
+ for (int i = 0; i < 32 * 32; ++i) {
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
+ }
+
+ // write new header
+ try {
+ this.flush();
+ this.file.force(true); // try to ensure it goes through...
+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex);
+ }
+ }
+
+ return true;
+ }
+
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
+
// Paper start - Cache chunk status
private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
@@ -80,8 +430,18 @@ public class RegionFile implements AutoCloseable {
public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
}
+ // Paper start - add can recalc flag
+ public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader);
+ }
public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException {
+ this(storageKey, path, directory, compressionFormat, dsync, true);
+ }
+
+ public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this.canRecalcHeader = canRecalcHeader;
+ // Paper end - add can recalc flag
this.header = ByteBuffer.allocateDirect(8192);
this.regionFile = file; // Paper
initOversizedState(); // Paper
@@ -112,14 +472,16 @@ public class RegionFile implements AutoCloseable {
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", path, i);
}
- long j = Files.size(path);
+ final long j = Files.size(path); final long regionFileSize = j; // Paper - recalculate header on header corruption
- for (int k = 0; k < 1024; ++k) {
- int l = this.offsets.get(k);
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
+ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location
+ final int l = this.offsets.get(k);
if (l != 0) {
- int i1 = RegionFile.getSectorNumber(l);
- int j1 = RegionFile.getNumSectors(l);
+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors
+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
// Spigot start
if (j1 == 255) {
// We're maxed out, so we need to read the proper length from the section
@@ -128,21 +490,66 @@ public class RegionFile implements AutoCloseable {
j1 = (realLen.getInt(0) + 4) / 4096 + 1;
}
// Spigot end
+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
if (i1 < 2) {
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1});
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if (j1 == 0) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, k);
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if ((long) i1 * 4096L > j) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{path, k, i1});
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else {
- this.usedSectors.force(i1, j1);
+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate
+ }
+ // Paper start - recalculate header on header corruption
+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
+ if (canRecalcHeader) {
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header...");
+ needsHeaderRecalc = true;
+ break;
+ } else {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ }
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength);
+ if (failedToAllocate) {
+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath());
}
+ if (failedToAllocate & !canRecalcHeader) {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ needsHeaderRecalc |= failedToAllocate;
+ // Paper end - recalculate header on header corruption
}
}
+ // Paper start - recalculate header on header corruption
+ // we move the recalc here so comparison to old header is correct when logging to console
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
+ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations");
+ this.recalculateHeader();
+ }
+ // Paper end
}
}
@@ -153,11 +560,36 @@ public class RegionFile implements AutoCloseable {
}
private Path getExternalChunkPath(ChunkPos chunkPos) {
- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
return this.externalFileDir.resolve(s);
}
+ // Paper start
+ private static ChunkPos getOversizedChunkPair(Path file) {
+ String fileName = file.getFileName().toString();
+
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
+ return null;
+ }
+
+ String[] split = fileName.split("\\.");
+
+ if (split.length != 4) {
+ return null;
+ }
+
+ try {
+ int x = Integer.parseInt(split[1]);
+ int z = Integer.parseInt(split[2]);
+
+ return new ChunkPos(x, z);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+ // Paper end
+
@Nullable
public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
int i = this.getOffset(pos);
@@ -181,6 +613,11 @@ public class RegionFile implements AutoCloseable {
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
if (bytebuffer.remaining() < 5) {
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
int i1 = bytebuffer.getInt();
@@ -188,6 +625,11 @@ public class RegionFile implements AutoCloseable {
if (i1 == 0) {
RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
int j1 = i1 - 1;
@@ -195,18 +637,45 @@ public class RegionFile implements AutoCloseable {
if (RegionFile.isExternalStreamChunk(b0)) {
if (j1 != 0) {
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
}
- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
} else if (j1 > bytebuffer.remaining()) {
RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()});
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else if (j1 < 0) {
RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, j1);
- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
}
}
}
@@ -392,10 +861,15 @@ public class RegionFile implements AutoCloseable {
}
private ByteBuffer createExternalStub() {
+ // Paper start - add version param
+ return this.createExternalStub(this.version);
+ }
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
+ // Paper end - add version param
ByteBuffer bytebuffer = ByteBuffer.allocate(5);
bytebuffer.putInt(1);
- bytebuffer.put((byte) (this.version.getId() | 128));
+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
return bytebuffer;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index a73a37320da2c141fc2db9d1d61233a34ce0c906..af50a02bafb7c1db4569604d1e69f95daab6d2a5 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -25,6 +25,7 @@ public class RegionFileStorage implements AutoCloseable {
private final RegionStorageInfo info;
private final Path folder;
private final boolean sync;
+ private final boolean isChunkData; // Paper
// Paper start - cache regionfile does not exist state
static final int MAX_NON_EXISTING_CACHE = 1024 * 64;
@@ -56,6 +57,12 @@ public class RegionFileStorage implements AutoCloseable {
// Paper end - cache regionfile does not exist state
protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected constructor
+ // Paper start - add isChunkData param
+ this(storageKey, directory, dsync, false);
+ }
+ RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync, boolean isChunkData) {
+ this.isChunkData = isChunkData;
+ // Paper end - add isChunkData param
this.folder = directory;
this.sync = dsync;
this.info = storageKey;
@@ -101,7 +108,7 @@ public class RegionFileStorage implements AutoCloseable {
// Paper - only create directory if not existing only - moved down
Path path = this.folder;
int j = chunkcoordintpair.getRegionX();
- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state
this.markNonExisting(regionPos);
return null; // CraftBukkit
@@ -110,7 +117,7 @@ public class RegionFileStorage implements AutoCloseable {
}
// Paper end - cache regionfile does not exist state
FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above
- RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync);
+ RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
this.regionCache.putAndMoveToFirst(i, regionfile1);
// Paper start
@@ -167,6 +174,13 @@ public class RegionFileStorage implements AutoCloseable {
if (regionfile == null) {
return null;
}
+ // Paper start - Add regionfile parameter
+ return this.read(pos, regionfile);
+ }
+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException {
+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
+ // if we decide to re-read
+ // Paper end
// CraftBukkit end
try { // Paper
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
@@ -183,6 +197,20 @@ public class RegionFileStorage implements AutoCloseable {
try {
if (datainputstream != null) {
nbttagcompound = NbtIo.read((DataInput) datainputstream);
+ // Paper start - recover from corrupt regionfile header
+ if (this.isChunkData) {
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
+ if (!chunkPos.equals(pos)) {
+ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath());
+ if (regionfile.recalculateHeader()) {
+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
+ return this.read(pos, regionfile);
+ }
+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath());
+ return null;
+ }
+ }
+ // Paper end - recover from corrupt regionfile header
break label43;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
index ef68b57ef1d8d7cb317c417569dd23a777fba4ad..f4a39f49b354c560d614483db1cd3dfc154e94b4 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -21,7 +21,7 @@ import org.slf4j.Logger;
public class RegionFileVersion {
private static final Logger LOGGER = LogUtils.getLogger();
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
new RegionFileVersion(