mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-08 03:22:19 +01:00
31fc02af68
Detect when the server has been hung for a long duration, and start printing thread dumps at an interval until the point of crash. This will help diagnose what was going on in that time before the crash.
1207 lines
61 KiB
Diff
1207 lines
61 KiB
Diff
--- a/net/minecraft/server/MinecraftServer.java
|
|
+++ b/net/minecraft/server/MinecraftServer.java
|
|
@@ -3,6 +3,9 @@
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.base.Splitter;
|
|
import com.google.common.collect.ImmutableList;
|
|
+import co.aikar.timings.Timings;
|
|
+import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
|
|
+import com.google.common.base.Stopwatch;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Sets;
|
|
@@ -45,7 +48,6 @@
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
-import java.util.concurrent.RejectedExecutionException;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
import java.util.function.BooleanSupplier;
|
|
@@ -84,17 +86,6 @@
|
|
import net.minecraft.obfuscate.DontObfuscate;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
-import net.minecraft.server.bossevents.CustomBossEvents;
|
|
-import net.minecraft.server.level.DemoMode;
|
|
-import net.minecraft.server.level.PlayerRespawnLogic;
|
|
-import net.minecraft.server.level.ServerChunkCache;
|
|
-import net.minecraft.server.level.ServerLevel;
|
|
-import net.minecraft.server.level.ServerPlayer;
|
|
-import net.minecraft.server.level.ServerPlayerGameMode;
|
|
-import net.minecraft.server.level.progress.ChunkProgressListener;
|
|
-import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
|
|
-import net.minecraft.server.network.ServerConnectionListener;
|
|
-import net.minecraft.server.network.TextFilter;
|
|
import net.minecraft.server.packs.PackType;
|
|
import net.minecraft.server.packs.repository.Pack;
|
|
import net.minecraft.server.packs.repository.PackRepository;
|
|
@@ -116,6 +107,7 @@
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.SignatureValidator;
|
|
import net.minecraft.util.TimeUtil;
|
|
+import net.minecraft.util.datafix.DataFixers;
|
|
import net.minecraft.util.debugchart.RemoteDebugSampleType;
|
|
import net.minecraft.util.debugchart.SampleLogger;
|
|
import net.minecraft.util.debugchart.TpsDebugDimensions;
|
|
@@ -156,37 +148,71 @@
|
|
import net.minecraft.world.level.biome.BiomeManager;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.entity.FuelValues;
|
|
-import net.minecraft.world.level.border.BorderChangeListener;
|
|
import net.minecraft.world.level.border.WorldBorder;
|
|
import net.minecraft.world.level.chunk.storage.ChunkIOErrorReporter;
|
|
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
|
import net.minecraft.world.level.dimension.LevelStem;
|
|
-import net.minecraft.world.level.levelgen.Heightmap;
|
|
-import net.minecraft.world.level.levelgen.PatrolSpawner;
|
|
-import net.minecraft.world.level.levelgen.PhantomSpawner;
|
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
|
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
+import net.minecraft.world.level.storage.WorldData;
|
|
+import org.slf4j.Logger;
|
|
+
|
|
+// CraftBukkit start
|
|
+import com.mojang.serialization.Dynamic;
|
|
+import com.mojang.serialization.Lifecycle;
|
|
+import java.io.File;
|
|
+import java.util.Random;
|
|
+// import jline.console.ConsoleReader; // Paper
|
|
+import joptsimple.OptionSet;
|
|
+import net.minecraft.nbt.NbtException;
|
|
+import net.minecraft.nbt.ReportedNbtException;
|
|
+import net.minecraft.server.bossevents.CustomBossEvents;
|
|
+import net.minecraft.server.dedicated.DedicatedServer;
|
|
+import net.minecraft.server.dedicated.DedicatedServerProperties;
|
|
+import net.minecraft.server.level.DemoMode;
|
|
+import net.minecraft.server.level.PlayerRespawnLogic;
|
|
+import net.minecraft.server.level.ServerChunkCache;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.ServerPlayerGameMode;
|
|
+import net.minecraft.server.level.progress.ChunkProgressListener;
|
|
+import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
|
|
+import net.minecraft.server.network.ServerConnectionListener;
|
|
+import net.minecraft.server.network.TextFilter;
|
|
+import net.minecraft.world.level.levelgen.Heightmap;
|
|
+import net.minecraft.world.level.levelgen.PatrolSpawner;
|
|
+import net.minecraft.world.level.levelgen.PhantomSpawner;
|
|
+import net.minecraft.world.level.levelgen.WorldDimensions;
|
|
+import net.minecraft.world.level.levelgen.presets.WorldPresets;
|
|
import net.minecraft.world.level.storage.CommandStorage;
|
|
-import net.minecraft.world.level.storage.DerivedLevelData;
|
|
import net.minecraft.world.level.storage.DimensionDataStorage;
|
|
import net.minecraft.world.level.storage.LevelData;
|
|
+import net.minecraft.world.level.storage.LevelDataAndDimensions;
|
|
import net.minecraft.world.level.storage.LevelResource;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
+import net.minecraft.world.level.storage.LevelSummary;
|
|
import net.minecraft.world.level.storage.PlayerDataStorage;
|
|
+import net.minecraft.world.level.storage.PrimaryLevelData;
|
|
import net.minecraft.world.level.storage.ServerLevelData;
|
|
-import net.minecraft.world.level.storage.WorldData;
|
|
+import net.minecraft.world.level.validation.ContentValidationException;
|
|
import net.minecraft.world.phys.Vec2;
|
|
import net.minecraft.world.phys.Vec3;
|
|
-import org.slf4j.Logger;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.craftbukkit.CraftRegistry;
|
|
+import org.bukkit.event.server.ServerLoadEvent;
|
|
+// CraftBukkit end
|
|
|
|
+
|
|
public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, ChunkIOErrorReporter, CommandSource {
|
|
|
|
+ private static MinecraftServer SERVER; // Paper
|
|
public static final Logger LOGGER = LogUtils.getLogger();
|
|
+ public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
|
|
public static final String VANILLA_BRAND = "vanilla";
|
|
private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F;
|
|
private static final int TICK_STATS_SPAN = 100;
|
|
- private static final long OVERLOADED_THRESHOLD_NANOS = 20L * TimeUtil.NANOSECONDS_PER_SECOND / 20L;
|
|
+ private static final long OVERLOADED_THRESHOLD_NANOS = 30L * TimeUtil.NANOSECONDS_PER_SECOND / 20L; // CraftBukkit
|
|
private static final int OVERLOADED_TICKS_THRESHOLD = 20;
|
|
private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
|
|
private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
|
|
@@ -224,6 +250,7 @@
|
|
private Map<ResourceKey<Level>, ServerLevel> levels;
|
|
private PlayerList playerList;
|
|
private volatile boolean running;
|
|
+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
|
|
private boolean stopped;
|
|
private int tickCount;
|
|
private int ticksUntilAutosave;
|
|
@@ -232,8 +259,7 @@
|
|
private boolean preventProxyConnections;
|
|
private boolean pvp;
|
|
private boolean allowFlight;
|
|
- @Nullable
|
|
- private String motd;
|
|
+ private net.kyori.adventure.text.Component motd; // Paper - Adventure
|
|
private int playerIdleTimeout;
|
|
private final long[] tickTimesNanos;
|
|
private long aggregatedTickTimesNanos;
|
|
@@ -277,6 +303,26 @@
|
|
private final SuppressedExceptionCollector suppressedExceptions;
|
|
private final DiscontinuousFrame tickFrame;
|
|
|
|
+ // CraftBukkit start
|
|
+ public final WorldLoader.DataLoadContext worldLoader;
|
|
+ public org.bukkit.craftbukkit.CraftServer server;
|
|
+ public OptionSet options;
|
|
+ public org.bukkit.command.ConsoleCommandSender console;
|
|
+ public static int currentTick; // Paper - improve tick loop
|
|
+ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
|
|
+ public int autosavePeriod;
|
|
+ public Commands vanillaCommandDispatcher;
|
|
+ private boolean forceTicks;
|
|
+ // CraftBukkit end
|
|
+ // Spigot start
|
|
+ public static final int TPS = 20;
|
|
+ public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
|
|
+ private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop
|
|
+ @Deprecated(forRemoval = true) // Paper
|
|
+ public final double[] recentTps = new double[ 3 ];
|
|
+ // Spigot end
|
|
+ public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
|
|
+
|
|
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
|
|
AtomicReference<S> atomicreference = new AtomicReference();
|
|
Thread thread = new Thread(() -> {
|
|
@@ -290,15 +336,16 @@
|
|
thread.setPriority(8);
|
|
}
|
|
|
|
- S s0 = (MinecraftServer) serverFactory.apply(thread);
|
|
+ S s0 = serverFactory.apply(thread); // CraftBukkit - decompile error
|
|
|
|
atomicreference.set(s0);
|
|
thread.start();
|
|
return s0;
|
|
}
|
|
|
|
- public MinecraftServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, Proxy proxy, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) {
|
|
+ public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
|
|
super("Server");
|
|
+ SERVER = this; // Paper - better singleton
|
|
this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
|
|
this.onMetricsRecordingStopped = (methodprofilerresults) -> {
|
|
this.stopRecordingMetrics();
|
|
@@ -319,36 +366,68 @@
|
|
this.scoreboard = new ServerScoreboard(this);
|
|
this.customBossEvents = new CustomBossEvents();
|
|
this.suppressedExceptions = new SuppressedExceptionCollector();
|
|
- this.registries = saveLoader.registries();
|
|
- this.worldData = saveLoader.worldData();
|
|
- if (!this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) {
|
|
+ this.registries = worldstem.registries();
|
|
+ this.worldData = worldstem.worldData();
|
|
+ if (false && !this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { // CraftBukkit - initialised later
|
|
throw new IllegalStateException("Missing Overworld dimension data");
|
|
} else {
|
|
this.proxy = proxy;
|
|
- this.packRepository = dataPackManager;
|
|
- this.resources = new MinecraftServer.ReloadableResources(saveLoader.resourceManager(), saveLoader.dataPackResources());
|
|
- this.services = apiServices;
|
|
- if (apiServices.profileCache() != null) {
|
|
- apiServices.profileCache().setExecutor(this);
|
|
+ this.packRepository = resourcepackrepository;
|
|
+ this.resources = new MinecraftServer.ReloadableResources(worldstem.resourceManager(), worldstem.dataPackResources());
|
|
+ this.services = services;
|
|
+ if (services.profileCache() != null) {
|
|
+ services.profileCache().setExecutor(this);
|
|
}
|
|
|
|
- this.connection = new ServerConnectionListener(this);
|
|
+ // this.connection = new ServerConnection(this); // Spigot
|
|
this.tickRateManager = new ServerTickRateManager(this);
|
|
- this.progressListenerFactory = worldGenerationProgressListenerFactory;
|
|
- this.storageSource = session;
|
|
- this.playerDataStorage = session.createPlayerStorage();
|
|
- this.fixerUpper = dataFixer;
|
|
+ this.progressListenerFactory = worldloadlistenerfactory;
|
|
+ this.storageSource = convertable_conversionsession;
|
|
+ this.playerDataStorage = convertable_conversionsession.createPlayerStorage();
|
|
+ this.fixerUpper = datafixer;
|
|
this.functionManager = new ServerFunctionManager(this, this.resources.managers.getFunctionLibrary());
|
|
HolderGetter<Block> holdergetter = this.registries.compositeAccess().lookupOrThrow(Registries.BLOCK).filterFeatures(this.worldData.enabledFeatures());
|
|
|
|
- this.structureTemplateManager = new StructureTemplateManager(saveLoader.resourceManager(), session, dataFixer, holdergetter);
|
|
- this.serverThread = serverThread;
|
|
+ this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer, holdergetter);
|
|
+ this.serverThread = thread;
|
|
this.executor = Util.backgroundExecutor();
|
|
this.potionBrewing = PotionBrewing.bootstrap(this.worldData.enabledFeatures());
|
|
this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
|
|
this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
|
|
this.tickFrame = TracyClient.createDiscontinuousFrame("Server Tick");
|
|
}
|
|
+ // CraftBukkit start
|
|
+ this.options = options;
|
|
+ this.worldLoader = worldLoader;
|
|
+ this.vanillaCommandDispatcher = worldstem.dataPackResources().commands; // CraftBukkit
|
|
+ // Paper start - Handled by TerminalConsoleAppender
|
|
+ // Try to see if we're actually running in a terminal, disable jline if not
|
|
+ /*
|
|
+ if (System.console() == null && System.getProperty("jline.terminal") == null) {
|
|
+ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
|
|
+ Main.useJline = false;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ this.reader = new ConsoleReader(System.in, System.out);
|
|
+ this.reader.setExpandEvents(false); // Avoid parsing exceptions for uncommonly used event designators
|
|
+ } catch (Throwable e) {
|
|
+ try {
|
|
+ // Try again with jline disabled for Windows users without C++ 2008 Redistributable
|
|
+ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
|
|
+ System.setProperty("user.language", "en");
|
|
+ Main.useJline = false;
|
|
+ this.reader = new ConsoleReader(System.in, System.out);
|
|
+ this.reader.setExpandEvents(false);
|
|
+ } catch (IOException ex) {
|
|
+ MinecraftServer.LOGGER.warn((String) null, ex);
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+ // Paper end
|
|
+ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
|
|
+ // CraftBukkit end
|
|
+ this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
|
|
}
|
|
|
|
private void readScoreboard(DimensionDataStorage persistentStateManager) {
|
|
@@ -357,7 +436,7 @@
|
|
|
|
protected abstract boolean initServer() throws IOException;
|
|
|
|
- protected void loadLevel() {
|
|
+ protected void loadLevel(String s) { // CraftBukkit
|
|
if (!JvmProfiler.INSTANCE.isRunning()) {
|
|
;
|
|
}
|
|
@@ -365,12 +444,8 @@
|
|
boolean flag = false;
|
|
ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
|
|
|
|
- this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
|
|
- ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
|
|
+ this.loadWorld0(s); // CraftBukkit
|
|
|
|
- this.createLevels(worldloadlistener);
|
|
- this.forceDifficulty();
|
|
- this.prepareLevels(worldloadlistener);
|
|
if (profiledduration != null) {
|
|
profiledduration.finish(true);
|
|
}
|
|
@@ -387,23 +462,232 @@
|
|
|
|
protected void forceDifficulty() {}
|
|
|
|
- protected void createLevels(ChunkProgressListener worldGenerationProgressListener) {
|
|
- ServerLevelData iworlddataserver = this.worldData.overworldData();
|
|
- boolean flag = this.worldData.isDebugWorld();
|
|
- Registry<LevelStem> iregistry = this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM);
|
|
- WorldOptions worldoptions = this.worldData.worldGenOptions();
|
|
- long i = worldoptions.seed();
|
|
- long j = BiomeManager.obfuscateSeed(i);
|
|
- List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
|
|
- LevelStem worlddimension = (LevelStem) iregistry.getValue(LevelStem.OVERWORLD);
|
|
- ServerLevel worldserver = new ServerLevel(this, this.executor, this.storageSource, iworlddataserver, Level.OVERWORLD, worlddimension, worldGenerationProgressListener, flag, j, list, true, (RandomSequences) null);
|
|
+ // CraftBukkit start
|
|
+ private void loadWorld0(String s) {
|
|
+ LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
|
|
|
|
- this.levels.put(Level.OVERWORLD, worldserver);
|
|
- DimensionDataStorage worldpersistentdata = worldserver.getDataStorage();
|
|
+ RegistryAccess.Frozen iregistrycustom_dimension = this.registries.compositeAccess();
|
|
+ Registry<LevelStem> dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
|
|
+ for (LevelStem worldDimension : dimensions) {
|
|
+ ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
|
|
|
|
- this.readScoreboard(worldpersistentdata);
|
|
- this.commandStorage = new CommandStorage(worldpersistentdata);
|
|
+ ServerLevel world;
|
|
+ int dimension = 0;
|
|
+
|
|
+ if (dimensionKey == LevelStem.NETHER) {
|
|
+ if (this.server.getAllowNether()) {
|
|
+ dimension = -1;
|
|
+ } else {
|
|
+ continue;
|
|
+ }
|
|
+ } else if (dimensionKey == LevelStem.END) {
|
|
+ if (this.server.getAllowEnd()) {
|
|
+ dimension = 1;
|
|
+ } else {
|
|
+ continue;
|
|
+ }
|
|
+ } else if (dimensionKey != LevelStem.OVERWORLD) {
|
|
+ dimension = -999;
|
|
+ }
|
|
+
|
|
+ String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT);
|
|
+ String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
|
|
+ if (dimension != 0) {
|
|
+ File newWorld = LevelStorageSource.getStorageFolder(new File(name).toPath(), dimensionKey).toFile();
|
|
+ File oldWorld = LevelStorageSource.getStorageFolder(new File(s).toPath(), dimensionKey).toFile();
|
|
+ File oldLevelDat = new File(new File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
|
|
+
|
|
+ if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
|
|
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
|
|
+ MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
|
|
+ MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
|
|
+ MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
|
|
+
|
|
+ if (newWorld.exists()) {
|
|
+ MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!");
|
|
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
+ } else if (newWorld.getParentFile().mkdirs()) {
|
|
+ if (oldWorld.renameTo(newWorld)) {
|
|
+ MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
|
|
+ // Migrate world data too.
|
|
+ try {
|
|
+ com.google.common.io.Files.copy(oldLevelDat, new File(new File(name), "level.dat"));
|
|
+ org.apache.commons.io.FileUtils.copyDirectory(new File(new File(s), "data"), new File(new File(name), "data"));
|
|
+ } catch (IOException exception) {
|
|
+ MinecraftServer.LOGGER.warn("Unable to migrate world data.");
|
|
+ }
|
|
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----");
|
|
+ } else {
|
|
+ MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!");
|
|
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
+ }
|
|
+ } else {
|
|
+ MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!");
|
|
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
|
|
+ } catch (IOException | ContentValidationException ex) {
|
|
+ throw new RuntimeException(ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ Dynamic<?> dynamic;
|
|
+ if (worldSession.hasWorldData()) {
|
|
+ LevelSummary worldinfo;
|
|
+
|
|
+ try {
|
|
+ dynamic = worldSession.getDataTag();
|
|
+ worldinfo = worldSession.getSummary(dynamic);
|
|
+ } catch (NbtException | ReportedNbtException | IOException ioexception) {
|
|
+ LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
|
|
+
|
|
+ MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
|
|
+ MinecraftServer.LOGGER.info("Attempting to use fallback");
|
|
+
|
|
+ try {
|
|
+ dynamic = worldSession.getDataTagFallback();
|
|
+ worldinfo = worldSession.getSummary(dynamic);
|
|
+ } catch (NbtException | ReportedNbtException | IOException ioexception1) {
|
|
+ MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
|
|
+ MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ worldSession.restoreLevelDataFromOld();
|
|
+ }
|
|
+
|
|
+ if (worldinfo.requiresManualConversion()) {
|
|
+ MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!worldinfo.isCompatible()) {
|
|
+ MinecraftServer.LOGGER.info("This world was created by an incompatible version.");
|
|
+ return;
|
|
+ }
|
|
+ } else {
|
|
+ dynamic = null;
|
|
+ }
|
|
+
|
|
+ org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
|
|
+ org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name);
|
|
+
|
|
+ PrimaryLevelData worlddata;
|
|
+ WorldLoader.DataLoadContext worldloader_a = this.worldLoader;
|
|
+ Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
|
|
+ if (dynamic != null) {
|
|
+ LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
|
|
+
|
|
+ worlddata = (PrimaryLevelData) leveldataanddimensions.worldData();
|
|
+ } else {
|
|
+ LevelSettings worldsettings;
|
|
+ WorldOptions worldoptions;
|
|
+ WorldDimensions worlddimensions;
|
|
+
|
|
+ if (this.isDemo()) {
|
|
+ worldsettings = MinecraftServer.DEMO_SETTINGS;
|
|
+ worldoptions = WorldOptions.DEMO_OPTIONS;
|
|
+ worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
|
|
+ } else {
|
|
+ DedicatedServerProperties dedicatedserverproperties = ((DedicatedServer) this).getProperties();
|
|
+
|
|
+ worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
|
|
+ worldoptions = this.options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
|
|
+ worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
|
|
+ }
|
|
+
|
|
+ WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
|
|
+ Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
|
|
+
|
|
+ worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
|
|
+ }
|
|
+ worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
|
|
+ if (this.options.has("forceUpgrade")) {
|
|
+ net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
|
|
+ return true;
|
|
+ }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
|
|
+ }
|
|
+
|
|
+ PrimaryLevelData iworlddataserver = worlddata;
|
|
+ boolean flag = worlddata.isDebugWorld();
|
|
+ WorldOptions worldoptions = worlddata.worldGenOptions();
|
|
+ long i = worldoptions.seed();
|
|
+ long j = BiomeManager.obfuscateSeed(i);
|
|
+ List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
|
|
+ LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey);
|
|
+
|
|
+ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value());
|
|
+ if (biomeProvider == null && gen != null) {
|
|
+ biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
|
|
+ }
|
|
+
|
|
+ ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
|
|
+
|
|
+ if (dimensionKey == LevelStem.OVERWORLD) {
|
|
+ this.worldData = worlddata;
|
|
+ this.worldData.setGameType(((DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
|
|
+
|
|
+ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
|
|
+
|
|
+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
|
|
+ DimensionDataStorage worldpersistentdata = world.getDataStorage();
|
|
+ this.readScoreboard(worldpersistentdata);
|
|
+ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
|
|
+ this.commandStorage = new CommandStorage(worldpersistentdata);
|
|
+ } else {
|
|
+ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
|
|
+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, ImmutableList.of(), true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
|
|
+ }
|
|
+
|
|
+ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
|
|
+ this.initWorld(world, worlddata, this.worldData, worldoptions);
|
|
+
|
|
+ this.addLevel(world);
|
|
+ this.getPlayerList().addWorldborderListener(world);
|
|
+
|
|
+ if (worlddata.getCustomBossEvents() != null) {
|
|
+ this.getCustomBossEvents().load(worlddata.getCustomBossEvents(), this.registryAccess());
|
|
+ }
|
|
+ }
|
|
+ this.forceDifficulty();
|
|
+ for (ServerLevel worldserver : this.getAllLevels()) {
|
|
+ this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
|
|
+ worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
|
|
+ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
|
|
+ }
|
|
+
|
|
+ // Paper start - Configurable player collision; Handle collideRule team for player collision toggle
|
|
+ final ServerScoreboard scoreboard = this.getScoreboard();
|
|
+ final java.util.Collection<String> toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList());
|
|
+ for (String teamName : toRemove) {
|
|
+ scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves
|
|
+ }
|
|
+
|
|
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) {
|
|
+ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16);
|
|
+ net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName);
|
|
+ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all
|
|
+ }
|
|
+ // Paper end - Configurable player collision
|
|
+
|
|
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
|
|
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
|
|
+ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
|
|
+ this.connection.acceptConnections();
|
|
+ }
|
|
+
|
|
+ public void initWorld(ServerLevel worldserver, ServerLevelData iworlddataserver, WorldData saveData, WorldOptions worldoptions) {
|
|
+ boolean flag = saveData.isDebugWorld();
|
|
+ // CraftBukkit start
|
|
+ if (worldserver.generator != null) {
|
|
+ worldserver.getWorld().getPopulators().addAll(worldserver.generator.getDefaultPopulators(worldserver.getWorld()));
|
|
+ }
|
|
WorldBorder worldborder = worldserver.getWorldBorder();
|
|
+ worldborder.applySettings(iworlddataserver.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent
|
|
+ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(worldserver.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated
|
|
|
|
if (!iworlddataserver.isInitialized()) {
|
|
try {
|
|
@@ -427,30 +711,8 @@
|
|
iworlddataserver.setInitialized(true);
|
|
}
|
|
|
|
- this.getPlayerList().addWorldborderListener(worldserver);
|
|
- if (this.worldData.getCustomBossEvents() != null) {
|
|
- this.getCustomBossEvents().load(this.worldData.getCustomBossEvents(), this.registryAccess());
|
|
- }
|
|
-
|
|
- RandomSequences randomsequences = worldserver.getRandomSequences();
|
|
- Iterator iterator = iregistry.entrySet().iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- Entry<ResourceKey<LevelStem>, LevelStem> entry = (Entry) iterator.next();
|
|
- ResourceKey<LevelStem> resourcekey = (ResourceKey) entry.getKey();
|
|
-
|
|
- if (resourcekey != LevelStem.OVERWORLD) {
|
|
- ResourceKey<Level> resourcekey1 = ResourceKey.create(Registries.DIMENSION, resourcekey.location());
|
|
- DerivedLevelData secondaryworlddata = new DerivedLevelData(this.worldData, iworlddataserver);
|
|
- ServerLevel worldserver1 = new ServerLevel(this, this.executor, this.storageSource, secondaryworlddata, resourcekey1, (LevelStem) entry.getValue(), worldGenerationProgressListener, flag, j, ImmutableList.of(), false, randomsequences);
|
|
-
|
|
- worldborder.addListener(new BorderChangeListener.DelegateBorderChangeListener(worldserver1.getWorldBorder()));
|
|
- this.levels.put(resourcekey1, worldserver1);
|
|
- }
|
|
- }
|
|
-
|
|
- worldborder.applySettings(iworlddataserver.getWorldBorder());
|
|
}
|
|
+ // CraftBukkit end
|
|
|
|
private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) {
|
|
if (debugWorld) {
|
|
@@ -458,6 +720,21 @@
|
|
} else {
|
|
ServerChunkCache chunkproviderserver = world.getChunkSource();
|
|
ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition());
|
|
+ // CraftBukkit start
|
|
+ if (world.generator != null) {
|
|
+ Random rand = new Random(world.getSeed());
|
|
+ org.bukkit.Location spawn = world.generator.getFixedSpawnLocation(world.getWorld(), rand);
|
|
+
|
|
+ if (spawn != null) {
|
|
+ if (spawn.getWorld() != world.getWorld()) {
|
|
+ throw new IllegalStateException("Cannot set spawn point for " + worldProperties.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")");
|
|
+ } else {
|
|
+ worldProperties.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw());
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
int i = chunkproviderserver.getGenerator().getSpawnHeight(world);
|
|
|
|
if (i < world.getMinY()) {
|
|
@@ -516,31 +793,36 @@
|
|
iworlddataserver.setGameType(GameType.SPECTATOR);
|
|
}
|
|
|
|
- public void prepareLevels(ChunkProgressListener worldGenerationProgressListener) {
|
|
- ServerLevel worldserver = this.overworld();
|
|
+ // CraftBukkit start
|
|
+ public void prepareLevels(ChunkProgressListener worldloadlistener, ServerLevel worldserver) {
|
|
+ // WorldServer worldserver = this.overworld();
|
|
+ this.forceTicks = true;
|
|
+ // CraftBukkit end
|
|
|
|
MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location());
|
|
BlockPos blockposition = worldserver.getSharedSpawnPos();
|
|
|
|
- worldGenerationProgressListener.updateSpawnPos(new ChunkPos(blockposition));
|
|
+ worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
|
|
ServerChunkCache chunkproviderserver = worldserver.getChunkSource();
|
|
|
|
this.nextTickTimeNanos = Util.getNanos();
|
|
worldserver.setDefaultSpawnPos(blockposition, worldserver.getSharedSpawnAngle());
|
|
- int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS);
|
|
+ int i = worldserver.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
|
|
int j = i > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(i)) : 0;
|
|
|
|
while (chunkproviderserver.getTickingGenerated() < j) {
|
|
- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
- this.waitUntilNextTick();
|
|
+ // CraftBukkit start
|
|
+ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
+ this.executeModerately();
|
|
}
|
|
|
|
- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
- this.waitUntilNextTick();
|
|
- Iterator iterator = this.levels.values().iterator();
|
|
+ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
+ this.executeModerately();
|
|
+ // Iterator iterator = this.levels.values().iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerLevel worldserver1 = (ServerLevel) iterator.next();
|
|
+ if (true) {
|
|
+ ServerLevel worldserver1 = worldserver;
|
|
+ // CraftBukkit end
|
|
ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
|
|
|
|
if (forcedchunk != null) {
|
|
@@ -555,10 +837,17 @@
|
|
}
|
|
}
|
|
|
|
- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
- this.waitUntilNextTick();
|
|
- worldGenerationProgressListener.stop();
|
|
- this.updateMobSpawningFlags();
|
|
+ // CraftBukkit start
|
|
+ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
|
|
+ this.executeModerately();
|
|
+ // CraftBukkit end
|
|
+ worldloadlistener.stop();
|
|
+ // CraftBukkit start
|
|
+ // this.updateMobSpawningFlags();
|
|
+ worldserver.setSpawnSettings(this.isSpawningMonsters());
|
|
+
|
|
+ this.forceTicks = false;
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public GameType getDefaultGameType() {
|
|
@@ -588,12 +877,16 @@
|
|
worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force);
|
|
}
|
|
|
|
- ServerLevel worldserver1 = this.overworld();
|
|
- ServerLevelData iworlddataserver = this.worldData.overworldData();
|
|
+ // CraftBukkit start - moved to WorldServer.save
|
|
+ /*
|
|
+ WorldServer worldserver1 = this.overworld();
|
|
+ IWorldDataServer iworlddataserver = this.worldData.overworldData();
|
|
|
|
iworlddataserver.setWorldBorder(worldserver1.getWorldBorder().createSettings());
|
|
this.worldData.setCustomBossEvents(this.getCustomBossEvents().save(this.registryAccess()));
|
|
this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData());
|
|
+ */
|
|
+ // CraftBukkit end
|
|
if (flush) {
|
|
Iterator iterator1 = this.getAllLevels().iterator();
|
|
|
|
@@ -628,18 +921,41 @@
|
|
this.stopServer();
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ private boolean hasStopped = false;
|
|
+ private final Object stopLock = new Object();
|
|
+ public final boolean hasStopped() {
|
|
+ synchronized (this.stopLock) {
|
|
+ return this.hasStopped;
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public void stopServer() {
|
|
+ // CraftBukkit start - prevent double stopping on multiple threads
|
|
+ synchronized(this.stopLock) {
|
|
+ if (this.hasStopped) return;
|
|
+ this.hasStopped = true;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (this.metricsRecorder.isRecording()) {
|
|
this.cancelRecordingMetrics();
|
|
}
|
|
|
|
MinecraftServer.LOGGER.info("Stopping server");
|
|
+ // CraftBukkit start
|
|
+ if (this.server != null) {
|
|
+ this.server.disablePlugins();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.shutdown(); // Paper - Plugin remapping
|
|
this.getConnection().stop();
|
|
this.isSaving = true;
|
|
if (this.playerList != null) {
|
|
MinecraftServer.LOGGER.info("Saving players");
|
|
this.playerList.saveAll();
|
|
- this.playerList.removeAll();
|
|
+ this.playerList.removeAll(this.isRestarting); // Paper
|
|
+ try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
|
|
}
|
|
|
|
MinecraftServer.LOGGER.info("Saving worlds");
|
|
@@ -693,6 +1009,15 @@
|
|
} catch (IOException ioexception1) {
|
|
MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
|
|
}
|
|
+ // Spigot start
|
|
+ io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.shutdown(); // Paper
|
|
+ try { io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
|
|
+ } catch (java.lang.InterruptedException ignored) {} // Paper
|
|
+ if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
|
+ MinecraftServer.LOGGER.info("Saving usercache.json");
|
|
+ this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
|
|
+ }
|
|
+ // Spigot end
|
|
|
|
}
|
|
|
|
@@ -709,6 +1034,12 @@
|
|
}
|
|
|
|
public void halt(boolean waitForShutdown) {
|
|
+ // Paper start - allow passing of the intent to restart
|
|
+ this.safeShutdown(waitForShutdown, false);
|
|
+ }
|
|
+ public void safeShutdown(boolean waitForShutdown, boolean isRestarting) {
|
|
+ this.isRestarting = isRestarting;
|
|
+ // Paper end
|
|
this.running = false;
|
|
if (waitForShutdown) {
|
|
try {
|
|
@@ -720,6 +1051,64 @@
|
|
|
|
}
|
|
|
|
+ // Spigot Start
|
|
+ private static double calcTps(double avg, double exp, double tps)
|
|
+ {
|
|
+ return ( avg * exp ) + ( tps * ( 1 - exp ) );
|
|
+ }
|
|
+
|
|
+ // Paper start - Further improve server tick loop
|
|
+ private static final long SEC_IN_NANO = 1000000000;
|
|
+ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
|
|
+ private long lastTick = 0;
|
|
+ private long catchupTime = 0;
|
|
+ public final RollingAverage tps1 = new RollingAverage(60);
|
|
+ public final RollingAverage tps5 = new RollingAverage(60 * 5);
|
|
+ public final RollingAverage tps15 = new RollingAverage(60 * 15);
|
|
+
|
|
+ public static class RollingAverage {
|
|
+ private final int size;
|
|
+ private long time;
|
|
+ private java.math.BigDecimal total;
|
|
+ private int index = 0;
|
|
+ private final java.math.BigDecimal[] samples;
|
|
+ private final long[] times;
|
|
+
|
|
+ RollingAverage(int size) {
|
|
+ this.size = size;
|
|
+ this.time = size * SEC_IN_NANO;
|
|
+ this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size));
|
|
+ this.samples = new java.math.BigDecimal[size];
|
|
+ this.times = new long[size];
|
|
+ for (int i = 0; i < size; i++) {
|
|
+ this.samples[i] = dec(TPS);
|
|
+ this.times[i] = SEC_IN_NANO;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static java.math.BigDecimal dec(long t) {
|
|
+ return new java.math.BigDecimal(t);
|
|
+ }
|
|
+ public void add(java.math.BigDecimal x, long t) {
|
|
+ time -= times[index];
|
|
+ total = total.subtract(samples[index].multiply(dec(times[index])));
|
|
+ samples[index] = x;
|
|
+ times[index] = t;
|
|
+ time += t;
|
|
+ total = total.add(x.multiply(dec(t)));
|
|
+ if (++index == size) {
|
|
+ index = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double getAverage() {
|
|
+ return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue();
|
|
+ }
|
|
+ }
|
|
+ private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL));
|
|
+ // Paper end
|
|
+ // Spigot End
|
|
+
|
|
protected void runServer() {
|
|
try {
|
|
if (!this.initServer()) {
|
|
@@ -727,9 +1116,16 @@
|
|
}
|
|
|
|
this.nextTickTimeNanos = Util.getNanos();
|
|
- this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse((Object) null);
|
|
+ this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse(null); // CraftBukkit - decompile error
|
|
this.status = this.buildServerStatus();
|
|
|
|
+ // Spigot start
|
|
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper
|
|
+ Arrays.fill( this.recentTps, 20 );
|
|
+ // Paper start - further improve server tick loop
|
|
+ long tickSection = Util.getNanos();
|
|
+ long currentTime;
|
|
+ // Paper end - further improve server tick loop
|
|
while (this.running) {
|
|
long i;
|
|
|
|
@@ -744,11 +1140,30 @@
|
|
if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) {
|
|
long k = j / i;
|
|
|
|
+ if (this.server.getWarnOnOverload()) // CraftBukkit
|
|
MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", j / TimeUtil.NANOSECONDS_PER_MILLISECOND, k);
|
|
this.nextTickTimeNanos += k * i;
|
|
this.lastOverloadWarningNanos = this.nextTickTimeNanos;
|
|
}
|
|
+ }
|
|
+ // Spigot start
|
|
+ // Paper start - further improve server tick loop
|
|
+ currentTime = Util.getNanos();
|
|
+ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
|
|
+ final long diff = currentTime - tickSection;
|
|
+ final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
|
|
+ tps1.add(currentTps, diff);
|
|
+ tps5.add(currentTps, diff);
|
|
+ tps15.add(currentTps, diff);
|
|
+
|
|
+ // Backwards compat with bad plugins
|
|
+ this.recentTps[0] = tps1.getAverage();
|
|
+ this.recentTps[1] = tps5.getAverage();
|
|
+ this.recentTps[2] = tps15.getAverage();
|
|
+ tickSection = currentTime;
|
|
}
|
|
+ // Paper end - further improve server tick loop
|
|
+ // Spigot end
|
|
|
|
boolean flag = i == 0L;
|
|
|
|
@@ -757,6 +1172,8 @@
|
|
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
|
|
}
|
|
|
|
+ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
|
|
+ lastTick = currentTime;
|
|
this.nextTickTimeNanos += i;
|
|
|
|
try {
|
|
@@ -830,6 +1247,13 @@
|
|
this.services.profileCache().clearExecutor();
|
|
}
|
|
|
|
+ org.spigotmc.WatchdogThread.doStop(); // Spigot
|
|
+ // CraftBukkit start - Restore terminal to original settings
|
|
+ try {
|
|
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
|
|
+ } catch (Exception ignored) {
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.onServerExit();
|
|
}
|
|
|
|
@@ -889,9 +1313,16 @@
|
|
}
|
|
|
|
private boolean haveTime() {
|
|
- return this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
|
|
+ // CraftBukkit start
|
|
+ return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
|
|
}
|
|
|
|
+ private void executeModerately() {
|
|
+ this.runAllTasks();
|
|
+ java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
|
|
+ // CraftBukkit end
|
|
+ }
|
|
+
|
|
public static boolean throwIfFatalException() {
|
|
RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get();
|
|
|
|
@@ -903,7 +1334,7 @@
|
|
}
|
|
|
|
public static void setFatalException(RuntimeException exception) {
|
|
- MinecraftServer.fatalException.compareAndSet((Object) null, exception);
|
|
+ MinecraftServer.fatalException.compareAndSet(null, exception); // CraftBukkit - decompile error
|
|
}
|
|
|
|
@Override
|
|
@@ -977,7 +1408,7 @@
|
|
}
|
|
}
|
|
|
|
- public void doRunTask(TickTask ticktask) {
|
|
+ public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error
|
|
Profiler.get().incrementCounter("runTask");
|
|
super.doRunTask(ticktask);
|
|
}
|
|
@@ -1025,6 +1456,7 @@
|
|
}
|
|
|
|
public void tickServer(BooleanSupplier shouldKeepTicking) {
|
|
+ org.spigotmc.WatchdogThread.tick(); // Spigot
|
|
long i = Util.getNanos();
|
|
int j = this.pauseWhileEmptySeconds() * 20;
|
|
|
|
@@ -1041,6 +1473,7 @@
|
|
this.autoSave();
|
|
}
|
|
|
|
+ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
|
|
this.tickConnection();
|
|
return;
|
|
}
|
|
@@ -1055,12 +1488,13 @@
|
|
}
|
|
|
|
--this.ticksUntilAutosave;
|
|
- if (this.ticksUntilAutosave <= 0) {
|
|
+ if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit
|
|
this.autoSave();
|
|
}
|
|
|
|
ProfilerFiller gameprofilerfiller = Profiler.get();
|
|
|
|
+ this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
|
|
gameprofilerfiller.push("tallying");
|
|
long k = Util.getNanos() - i;
|
|
int l = this.tickCount % 100;
|
|
@@ -1074,7 +1508,7 @@
|
|
}
|
|
|
|
private void autoSave() {
|
|
- this.ticksUntilAutosave = this.computeNextAutosaveInterval();
|
|
+ this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit
|
|
MinecraftServer.LOGGER.debug("Autosave started");
|
|
ProfilerFiller gameprofilerfiller = Profiler.get();
|
|
|
|
@@ -1123,7 +1557,7 @@
|
|
private ServerStatus buildServerStatus() {
|
|
ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus();
|
|
|
|
- return new ServerStatus(Component.nullToEmpty(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile());
|
|
+ return new ServerStatus(io.papermc.paper.adventure.PaperAdventure.asVanilla(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile()); // Paper - Adventure
|
|
}
|
|
|
|
private ServerStatus.Players buildPlayerStatus() {
|
|
@@ -1133,7 +1567,7 @@
|
|
if (this.hidesOnlinePlayers()) {
|
|
return new ServerStatus.Players(i, list.size(), List.of());
|
|
} else {
|
|
- int j = Math.min(list.size(), 12);
|
|
+ int j = Math.min(list.size(), org.spigotmc.SpigotConfig.playerSample); // Paper - PaperServerListPingEvent
|
|
ObjectArrayList<GameProfile> objectarraylist = new ObjectArrayList(j);
|
|
int k = Mth.nextInt(this.random, 0, list.size() - j);
|
|
|
|
@@ -1154,24 +1588,43 @@
|
|
this.getPlayerList().getPlayers().forEach((entityplayer) -> {
|
|
entityplayer.connection.suspendFlushing();
|
|
});
|
|
+ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
|
|
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
|
|
gameprofilerfiller.push("commandFunctions");
|
|
this.getFunctions().tick();
|
|
gameprofilerfiller.popPush("levels");
|
|
Iterator iterator = this.getAllLevels().iterator();
|
|
|
|
+ // CraftBukkit start
|
|
+ // Run tasks that are waiting on processing
|
|
+ while (!this.processQueue.isEmpty()) {
|
|
+ this.processQueue.remove().run();
|
|
+ }
|
|
+
|
|
+ // Send time updates to everyone, it will get the right time from the world the player is in.
|
|
+ if (this.tickCount % 20 == 0) {
|
|
+ for (int i = 0; i < this.getPlayerList().players.size(); ++i) {
|
|
+ ServerPlayer entityplayer = (ServerPlayer) this.getPlayerList().players.get(i);
|
|
+ entityplayer.connection.send(new ClientboundSetTimePacket(entityplayer.level().getGameTime(), entityplayer.getPlayerTime(), entityplayer.serverLevel().getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); // Add support for per player time
|
|
+ }
|
|
+ }
|
|
+
|
|
while (iterator.hasNext()) {
|
|
ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
+ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
|
|
|
|
gameprofilerfiller.push(() -> {
|
|
String s = String.valueOf(worldserver);
|
|
|
|
return s + " " + String.valueOf(worldserver.dimension().location());
|
|
});
|
|
+ /* Drop global time updates
|
|
if (this.tickCount % 20 == 0) {
|
|
gameprofilerfiller.push("timeSync");
|
|
this.synchronizeTime(worldserver);
|
|
gameprofilerfiller.pop();
|
|
}
|
|
+ // CraftBukkit end */
|
|
|
|
gameprofilerfiller.push("tick");
|
|
|
|
@@ -1186,6 +1639,7 @@
|
|
|
|
gameprofilerfiller.pop();
|
|
gameprofilerfiller.pop();
|
|
+ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
|
|
}
|
|
|
|
gameprofilerfiller.popPush("connection");
|
|
@@ -1265,8 +1719,24 @@
|
|
@Nullable
|
|
public ServerLevel getLevel(ResourceKey<Level> key) {
|
|
return (ServerLevel) this.levels.get(key);
|
|
+ }
|
|
+
|
|
+ // CraftBukkit start
|
|
+ public void addLevel(ServerLevel level) {
|
|
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
|
|
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
|
+ newLevels.put(level.dimension(), level);
|
|
+ this.levels = Collections.unmodifiableMap(newLevels);
|
|
}
|
|
|
|
+ public void removeLevel(ServerLevel level) {
|
|
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
|
|
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
|
+ newLevels.remove(level.dimension());
|
|
+ this.levels = Collections.unmodifiableMap(newLevels);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public Set<ResourceKey<Level>> levelKeys() {
|
|
return this.levels.keySet();
|
|
}
|
|
@@ -1296,7 +1766,7 @@
|
|
|
|
@DontObfuscate
|
|
public String getServerModName() {
|
|
- return "vanilla";
|
|
+ return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper
|
|
}
|
|
|
|
public SystemReport fillSystemReport(SystemReport details) {
|
|
@@ -1347,7 +1817,7 @@
|
|
|
|
@Override
|
|
public void sendSystemMessage(Component message) {
|
|
- MinecraftServer.LOGGER.info(message.getString());
|
|
+ MinecraftServer.LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(message))); // Paper - Log message with colors
|
|
}
|
|
|
|
public KeyPair getKeyPair() {
|
|
@@ -1481,10 +1951,20 @@
|
|
|
|
@Override
|
|
public String getMotd() {
|
|
- return this.motd;
|
|
+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.motd); // Paper - Adventure
|
|
}
|
|
|
|
public void setMotd(String motd) {
|
|
+ // Paper start - Adventure
|
|
+ this.motd = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOr(motd, net.kyori.adventure.text.Component.empty());
|
|
+ }
|
|
+
|
|
+ public net.kyori.adventure.text.Component motd() {
|
|
+ return this.motd;
|
|
+ }
|
|
+
|
|
+ public void motd(net.kyori.adventure.text.Component motd) {
|
|
+ // Paper end - Adventure
|
|
this.motd = motd;
|
|
}
|
|
|
|
@@ -1507,7 +1987,7 @@
|
|
}
|
|
|
|
public ServerConnectionListener getConnection() {
|
|
- return this.connection;
|
|
+ return this.connection == null ? this.connection = new ServerConnectionListener(this) : this.connection; // Spigot
|
|
}
|
|
|
|
public boolean isReady() {
|
|
@@ -1634,11 +2114,11 @@
|
|
|
|
public CompletableFuture<Void> reloadResources(Collection<String> dataPacks) {
|
|
CompletableFuture<Void> completablefuture = CompletableFuture.supplyAsync(() -> {
|
|
- Stream stream = dataPacks.stream();
|
|
+ Stream<String> stream = dataPacks.stream(); // CraftBukkit - decompile error
|
|
PackRepository resourcepackrepository = this.packRepository;
|
|
|
|
Objects.requireNonNull(this.packRepository);
|
|
- return (ImmutableList) stream.map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList());
|
|
+ return stream.<Pack>map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error // todo: is this needed anymore?
|
|
}, this).thenCompose((immutablelist) -> {
|
|
MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist);
|
|
List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess());
|
|
@@ -1654,6 +2134,7 @@
|
|
}).thenAcceptAsync((minecraftserver_reloadableresources) -> {
|
|
this.resources.close();
|
|
this.resources = minecraftserver_reloadableresources;
|
|
+ this.server.syncCommands(); // SPIGOT-5884: Lost on reload
|
|
this.packRepository.setSelected(dataPacks);
|
|
WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures());
|
|
|
|
@@ -1952,7 +2433,7 @@
|
|
final List<String> list = Lists.newArrayList();
|
|
final GameRules gamerules = this.getGameRules();
|
|
|
|
- gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor(this) {
|
|
+ gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor() { // CraftBukkit - decompile error
|
|
@Override
|
|
public <T extends GameRules.Value<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) {
|
|
list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key)));
|
|
@@ -2058,7 +2539,7 @@
|
|
try {
|
|
label51:
|
|
{
|
|
- ArrayList arraylist;
|
|
+ ArrayList<NativeModuleLister.NativeModuleInfo> arraylist; // CraftBukkit - decompile error
|
|
|
|
try {
|
|
arraylist = Lists.newArrayList(NativeModuleLister.listModules());
|
|
@@ -2108,6 +2589,21 @@
|
|
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ public boolean isDebugging() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public static MinecraftServer getServer() {
|
|
+ return SERVER; // Paper
|
|
+ }
|
|
+
|
|
+ @Deprecated
|
|
+ public static RegistryAccess getDefaultRegistryAccess() {
|
|
+ return CraftRegistry.getMinecraftRegistry();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
private ProfilerFiller createProfiler() {
|
|
if (this.willStartRecordingMetrics) {
|
|
this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> {
|
|
@@ -2225,18 +2721,24 @@
|
|
}
|
|
|
|
public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) {
|
|
- String s1 = params.decorate(message).getString();
|
|
+ // Paper start
|
|
+ net.kyori.adventure.text.Component s1 = io.papermc.paper.adventure.PaperAdventure.asAdventure(params.decorate(message));
|
|
|
|
if (prefix != null) {
|
|
- MinecraftServer.LOGGER.info("[{}] {}", prefix, s1);
|
|
+ MinecraftServer.COMPONENT_LOGGER.info("[{}] {}", prefix, s1);
|
|
} else {
|
|
- MinecraftServer.LOGGER.info("{}", s1);
|
|
+ MinecraftServer.COMPONENT_LOGGER.info("{}", s1);
|
|
+ // Paper end
|
|
}
|
|
|
|
}
|
|
|
|
+ public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
|
|
+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
|
|
+
|
|
+ public final ChatDecorator improvedChatDecorator = new io.papermc.paper.adventure.ImprovedChatDecorator(this); // Paper - adventure
|
|
public ChatDecorator getChatDecorator() {
|
|
- return ChatDecorator.PLAIN;
|
|
+ return this.improvedChatDecorator; // Paper - support async chat decoration events
|
|
}
|
|
|
|
public boolean logIPs() {
|