diff --git a/lophine-server/minecraft-patches/features/0005-Add-config-to-enable-save-all-command.patch b/lophine-server/minecraft-patches/features/0005-Add-config-to-enable-save-all-command.patch new file mode 100644 index 0000000..572a158 --- /dev/null +++ b/lophine-server/minecraft-patches/features/0005-Add-config-to-enable-save-all-command.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Helvetica Volubi +Date: Fri, 16 Jan 2026 02:34:43 +0800 +Subject: [PATCH] Add config to enable save-all command + + +diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java +index b3bc39ebe8d2f48d976de9cd4fcae46095523514..8d20f7e3d89c42e7b92bbaac5da85ac97fecc459 100644 +--- a/io/papermc/paper/threadedregions/RegionizedServer.java ++++ b/io/papermc/paper/threadedregions/RegionizedServer.java +@@ -350,6 +350,8 @@ public final class RegionizedServer { + // tick player ping sample + this.tickPlayerSample(); + ++ fun.bm.lophine.utils.SaveAllUtil.checkTimeout(); // Lophine - save-all fix ++ + // tick worlds + for (final ServerLevel world : this.worlds) { + this.globalTick(world, tickCount); +diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java +index 44e59c373538e3ab19cb25cdcbd9b51b6ab86e21..68c9e32ba6cccbcecfb71058a4bfd5130b572fd0 100644 +--- a/io/papermc/paper/threadedregions/TickRegions.java ++++ b/io/papermc/paper/threadedregions/TickRegions.java +@@ -190,6 +190,8 @@ public final class TickRegions implements ThreadedRegionizer.RegionCallbacks region; + public final ServerLevel world; + ++ public long lastSavedTime = 0; // Lophine - save-all fix ++ + // generic regionised data + private final Reference2ReferenceOpenHashMap, Object> regionizedData = new Reference2ReferenceOpenHashMap<>(); + +diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java +index b95d144dd479d339cc4b0f92cc4c3bade9af4927..097e5b6a14fd148c93ca3894295a4a6b1e9aa870 100644 +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -294,7 +294,11 @@ public class Commands { + PardonCommand.register(this.dispatcher); + PardonIpCommand.register(this.dispatcher); + //PerfCommand.register(this.dispatcher); // Folia - region threading - TODO later +- //SaveAllCommand.register(this.dispatcher); // Folia - region threading - TODO later ++ // Lophine start - Add a config to enable save all command ++ if (fun.bm.lophine.config.modules.experiment.CommandConfig.saveAll) { ++ SaveAllCommand.register(this.dispatcher); // Folia - region threading - TODO later ++ } ++ // Lophine end - Add a config to enable save all command + SaveOffCommand.register(this.dispatcher); + SaveOnCommand.register(this.dispatcher); + SetPlayerIdleTimeoutCommand.register(this.dispatcher); +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 491c78bffae2917f1f7f0b1fce0376a987319537..1600296f134c84ae8eee4e54f5ea54f19e6ed258 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1658,6 +1658,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Component.translatable("commands.save.saving"), false); ++ // Lophine start - save all ++ fun.bm.lophine.utils.SaveAllUtil.preSaveAll(source, flush); ++/* source.sendSuccess(() -> Component.translatable("commands.save.saving"), false); + MinecraftServer server = source.getServer(); + boolean flag = server.saveEverything(true, flush, true); + if (!flag) { +@@ -29,6 +31,12 @@ public class SaveAllCommand { + } else { + source.sendSuccess(() -> Component.translatable("commands.save.success"), true); + return 1; +- } ++ }*/ ++ return 1; + } ++ ++ public static SimpleCommandExceptionType getErrorFailed() { ++ return ERROR_FAILED; ++ } ++ // Lophine end - save all + } diff --git a/lophine-server/minecraft-patches/features/0005-Add-config-to-enable-scoreboard-command.patch b/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-scoreboard-command.patch similarity index 93% rename from lophine-server/minecraft-patches/features/0005-Add-config-to-enable-scoreboard-command.patch rename to lophine-server/minecraft-patches/features/0006-Add-config-to-enable-scoreboard-command.patch index 76a3329..3054106 100644 --- a/lophine-server/minecraft-patches/features/0005-Add-config-to-enable-scoreboard-command.patch +++ b/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-scoreboard-command.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add config to enable scoreboard command diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java -index b95d144dd479d339cc4b0f92cc4c3bade9af4927..44f469b1fc5169adf3214ac52b8e2647d26e7974 100644 +index 097e5b6a14fd148c93ca3894295a4a6b1e9aa870..bd8d98aa813b575e83138e79726bd3143268913c 100644 --- a/net/minecraft/commands/Commands.java +++ b/net/minecraft/commands/Commands.java @@ -241,7 +241,11 @@ public class Commands { diff --git a/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-tick-command.patch b/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-tick-command.patch similarity index 95% rename from lophine-server/minecraft-patches/features/0006-Add-config-to-enable-tick-command.patch rename to lophine-server/minecraft-patches/features/0007-Add-config-to-enable-tick-command.patch index d444bc8..772a94f 100644 --- a/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-tick-command.patch +++ b/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-tick-command.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add config to enable tick command diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java -index b3bc39ebe8d2f48d976de9cd4fcae46095523514..6df2b8cf3b902bb157ed5fa9eaf9ca062d9e5a39 100644 +index 8d20f7e3d89c42e7b92bbaac5da85ac97fecc459..f3be5b71abde2d9833e3b513845485d3030ef2c6 100644 --- a/io/papermc/paper/threadedregions/RegionizedServer.java +++ b/io/papermc/paper/threadedregions/RegionizedServer.java @@ -332,6 +332,11 @@ public final class RegionizedServer { @@ -20,7 +20,7 @@ index b3bc39ebe8d2f48d976de9cd4fcae46095523514..6df2b8cf3b902bb157ed5fa9eaf9ca06 // expire invalid click command callbacks io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue((int)this.tickCount); // Paper // Folia - region threading - moved to global tick io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue((int)this.tickCount); // Paper // Folia - region threading - moved to global tick -@@ -355,6 +360,13 @@ public final class RegionizedServer { +@@ -357,6 +362,13 @@ public final class RegionizedServer { this.globalTick(world, tickCount); } @@ -34,7 +34,7 @@ index b3bc39ebe8d2f48d976de9cd4fcae46095523514..6df2b8cf3b902bb157ed5fa9eaf9ca06 // tick connections this.tickConnections(); -@@ -488,7 +500,7 @@ public final class RegionizedServer { +@@ -490,7 +502,7 @@ public final class RegionizedServer { } private void tickTime(final ServerLevel world, final int tickCount) { @@ -97,7 +97,7 @@ index e3df94bfd26b118cb8a7bdece75d611aaf27c606..bae2385b725c0ce551768f22945393cc this.scheduler.regionFailed(this, false, thr); // regionFailed will schedule a shutdown, so we should avoid letting this region tick further diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java -index 8d77d6f165a5883e997122d1c2f231287e0caebe..23f047a47cdccc90a96153cc52bdedf91cd15db9 100644 +index bd8d98aa813b575e83138e79726bd3143268913c..65f200381f486146ce8d893378f0591a82f6076b 100644 --- a/net/minecraft/commands/Commands.java +++ b/net/minecraft/commands/Commands.java @@ -262,7 +262,11 @@ public class Commands { @@ -114,7 +114,7 @@ index 8d77d6f165a5883e997122d1c2f231287e0caebe..23f047a47cdccc90a96153cc52bdedf9 TitleCommand.register(this.dispatcher, context); //TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 1627a8f3ebf7bf1a750a62802f1187ad8c1a6a0f..6e18d988104c121d9683eb57e77a78acfdee3907 100644 +index 1600296f134c84ae8eee4e54f5ea54f19e6ed258..e40ce457fc046b3b244c67afa79e4f637cc73fde 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -270,7 +270,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Folia - region threading - this is complicated to implement, and even if done correctly is messy @@ -66,7 +66,7 @@ index 1124987a47bcdb4c64bcc842f30675f4da866261..e6c55ee206e7bf35fa2a3d8ce400a139 this.emptyTicks++; } else { this.emptyTicks = 0; -@@ -1798,6 +1805,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { this.playerList.saveAll(playerSaveInterval); @@ -74,7 +74,7 @@ index 1124987a47bcdb4c64bcc842f30675f4da866261..e6c55ee206e7bf35fa2a3d8ce400a139 } for (final ServerLevel level : (region == null ? this.getAllLevels() : Arrays.asList(region.world))) { // Folia - region threading if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { -@@ -2018,6 +2026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= resultLimit) { diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index e6c55ee206e7bf35fa2a3d8ce400a13932f249c8..30dccb37822c2267c4759804164cc4871fdb8c61 100644 +index ca199e6334f39d8598ccc22ef5aca36ecfbb4939..a32a32e19cfc07df86587cb8f7c7eabff19d97cd 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1893,7 +1893,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Folia - region threading // Paper start - Expand PlayerGameModeChangeEvent org.bukkit.event.player.PlayerGameModeChangeEvent event = player.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null); // Folia - region threading -@@ -2491,7 +2491,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop currentSaveAll = null; + private static volatile int regionCount = 0; + private static final AtomicInteger savedRegionCount = new AtomicInteger(0); + private static final AtomicBoolean withError = new AtomicBoolean(false); + + public static void preSaveAll(CommandSourceStack source, boolean flush) { + synchronized (lock) { + if (isSaving()) { + source.sendFailure(Component.literal("Server is already in saving! Please wait...")); + } else { + source.sendSuccess(() -> Component.translatable("commands.save.saving"), false); + currentSaveAll = Pair.of(source, flush); + // we need to count how many regions we need to save + AtomicInteger count = new AtomicInteger(); + for (ServerLevel world : source.getServer().getAllLevels()) { + world.regioniser.computeForAllRegions(unused -> { + count.getAndIncrement(); + }); + } + regionCount = count.get(); + savedRegionCount.set(0); + withError.set(false); + lastSaveAllTime = System.currentTimeMillis(); + } + } + } + + public static void postRegionSave(io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { + Pair currentSaveAll = SaveAllUtil.currentSaveAll; + synchronized (lock) { + if (lastSaveAllTime < region.lastSavedTime) return; // already saved + } + try { + region.world.moonrise$getChunkTaskScheduler().chunkHolderManager.saveAllChunksRegionised(currentSaveAll.getSecond(), false, CommandConfig.logAllProcess, false, false, true, false); + currentSaveAll.getFirst().getServer().getPlayerList().saveAll(); + MinecraftServer.LOGGER.info("Saved chunks in region around chunk {} in world '{}'", region.region.getCenterChunk(), region.region.regioniser.world.getWorld().getName()); + } catch (final Throwable thr) { + CommandSyntaxException error = SaveAllCommand.getErrorFailed().create(); + currentSaveAll.getFirst().sendFailure(Component.literal(error.getMessage())); + MinecraftServer.LOGGER.error(error.getMessage(), thr); + withError.set(true); + } + region.lastSavedTime = System.currentTimeMillis(); + int saved; + synchronized (savedRegionCount) { + saved = savedRegionCount.incrementAndGet(); + } + if (saved >= regionCount) { + if (withError.get()) { + currentSaveAll.getFirst().sendFailure(Component.literal("At least one region failed to save!")); + } else if (saved == regionCount) { + currentSaveAll.getFirst().sendSuccess(() -> Component.translatable("commands.save.success"), true); + SaveAllUtil.currentSaveAll = null; + } + } + } + + public static void checkTimeout() { + Pair currentSaveAll = SaveAllUtil.currentSaveAll; + if (!isSaving()) return; + if (System.currentTimeMillis() - lastSaveAllTime > TIMEOUT) { + currentSaveAll.getFirst().sendFailure(Component.literal("At least one region save data timeout!")); + currentSaveAll.getFirst().sendFailure(Component.literal("Regions need to save expect is " + regionCount + ", but only " + savedRegionCount.get() + " saved!")); + SaveAllUtil.currentSaveAll = null; + } + } + + public static boolean isSaving() { + synchronized (lock) { + return currentSaveAll != null; + } + } +}