diff --git a/patches/server/0003-Threaded-Regions.patch b/patches/server/0003-Threaded-Regions.patch index 4020e45..0ad3db9 100644 --- a/patches/server/0003-Threaded-Regions.patch +++ b/patches/server/0003-Threaded-Regions.patch @@ -1195,17 +1195,21 @@ index c03608fec96b51e1867f43d8f42e5aefb1520e46..127d96280cad2d4e5db574a089d67ad6 * on all currently scheduled tasks. diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7ad6c81edf +index 0000000000000000000000000000000000000000..261b3019878c31a9e44e56b6611899de6c00ebee --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java -@@ -0,0 +1,174 @@ +@@ -0,0 +1,226 @@ +package io.papermc.paper.threadedregions; + ++import ca.spottedleaf.moonrise.common.util.WorldUtil; +import com.mojang.logging.LogUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; ++import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; ++import org.bukkit.event.inventory.InventoryCloseEvent; +import org.slf4j.Logger; +import java.util.ArrayList; +import java.util.List; @@ -1314,18 +1318,42 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a + } + } + -+ private void haltWorldNoRegions(final ServerLevel world) { ++ private void closePlayerInventories(final ThreadedRegionizer.ThreadedRegion region) { ++ ChunkPos center = null; + try { -+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true, true, true, false); ++ this.shuttingDown = region; ++ center = region.getCenterChunk(); ++ ++ final RegionizedWorldData worldData = region.regioniser.world.worldRegionData.get(); ++ ++ for (final ServerPlayer player : worldData.getLocalPlayers()) { ++ try { ++ // close inventory ++ if (player.containerMenu != player.inventoryMenu) { ++ player.closeContainer(InventoryCloseEvent.Reason.DISCONNECT); ++ } ++ ++ // drop carried item ++ if (!player.containerMenu.getCarried().isEmpty()) { ++ ItemStack carried = player.containerMenu.getCarried(); ++ player.containerMenu.setCarried(ItemStack.EMPTY); ++ player.drop(carried, false); ++ } ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to close player inventory for player: " + player, thr); ++ } ++ } + } catch (final Throwable thr) { -+ LOGGER.error("Failed to close world '" + world.getWorld().getName() + "' with no regions", thr); ++ LOGGER.error("Failed to close player inventories for region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'", thr); ++ } finally { ++ this.shuttingDown = null; + } + } + + @Override + public final void run() { + // await scheduler termination -+ LOGGER.info("Awaiting scheduler termination for 60s"); ++ LOGGER.info("Awaiting scheduler termination for 60s..."); + if (TickRegions.getScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) { + LOGGER.info("Scheduler halted"); + } else { @@ -1335,7 +1363,7 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a + + MinecraftServer.getServer().stopServer(); // stop part 1: most logic, kicking players, plugins, etc + // halt all chunk systems first so that any in-progress chunk generation stops -+ LOGGER.info("Halting chunk systems"); ++ LOGGER.info("Halting chunk systems..."); + for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) { + try { + world.moonrise$getChunkTaskScheduler().halt(false, 0L); @@ -1347,27 +1375,51 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a + this.haltChunkSystem(world); + } + LOGGER.info("Halted chunk systems"); ++ ++ LOGGER.info("Finishing pending teleports..."); + for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) { + final List> + regions = new ArrayList<>(); + world.regioniser.computeForAllRegionsUnsynchronised(regions::add); + + for (int i = 0, len = regions.size(); i < len; ++i) { -+ final ThreadedRegionizer.ThreadedRegion region = regions.get(i); -+ this.finishTeleportations(region, world); ++ this.finishTeleportations(regions.get(i), world); + } -+ -+ for (int i = 0, len = regions.size(); i < len; ++i) { -+ final ThreadedRegionizer.ThreadedRegion region = regions.get(i); -+ this.saveRegionChunks(region, (i + 1) == len); -+ } -+ -+ this.saveLevelData(world); + } -+ // moved from stop part 1 -+ // we need this to be after saving level data, as that will complete any teleportations the player is in -+ LOGGER.info("Saving players"); ++ LOGGER.info("Finished pending teleports"); ++ ++ LOGGER.info("Saving all worlds"); ++ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) { ++ LOGGER.info("Saving world data for world '" + WorldUtil.getWorldName(world) + "'"); ++ ++ final List> ++ regions = new ArrayList<>(); ++ world.regioniser.computeForAllRegionsUnsynchronised(regions::add); ++ ++ LOGGER.info("Closing player inventories..."); ++ for (int i = 0, len = regions.size(); i < len; ++i) { ++ this.closePlayerInventories(regions.get(i)); ++ } ++ LOGGER.info("Closed player inventories"); ++ ++ LOGGER.info("Saving chunks..."); ++ for (int i = 0, len = regions.size(); i < len; ++i) { ++ this.saveRegionChunks(regions.get(i), (i + 1) == len); ++ } ++ LOGGER.info("Saved chunks"); ++ ++ LOGGER.info("Saving level data..."); ++ this.saveLevelData(world); ++ LOGGER.info("Saved level data"); ++ ++ LOGGER.info("Saved world data for world '" + WorldUtil.getWorldName(world) + "'"); ++ } ++ LOGGER.info("Saved all worlds"); ++ ++ // Note: only save after world data and pending teleportations ++ LOGGER.info("Saving all player data..."); + MinecraftServer.getServer().getPlayerList().saveAll(); ++ LOGGER.info("Saved all player data"); + + MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc) + // done, part 2 should call exit()