diff --git a/patches/server/0005-Leaves-Server-Config-And-Command.patch b/patches/server/0005-Leaves-Server-Config-And-Command.patch index 6d3c37e4..67607e13 100644 --- a/patches/server/0005-Leaves-Server-Config-And-Command.patch +++ b/patches/server/0005-Leaves-Server-Config-And-Command.patch @@ -78,10 +78,10 @@ index cac0f51ff4f6b1bc498d24fa8ecdb910b3d8f010..0373c1cc266316fc29a789e0f193935e .withRequiredArg() diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..a25273c37a36036d1014f754db07b51c9287d382 +index 0000000000000000000000000000000000000000..18ee98567138968dce3322e7ba943efba899d798 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java -@@ -0,0 +1,892 @@ +@@ -0,0 +1,895 @@ +package top.leavesmc.leaves; + +import com.destroystokyo.paper.util.SneakyThrow; @@ -565,6 +565,9 @@ index 0000000000000000000000000000000000000000..a25273c37a36036d1014f754db07b51c + } + } + ++ @GlobalConfig(name = "fast-resume", category = "modify") ++ public static boolean fastResume = false; ++ + // Leaves start - modify - removed + + @RemovedConfig diff --git a/patches/server/0133-Fast-resume.patch b/patches/server/0133-Fast-resume.patch new file mode 100644 index 00000000..3c1d204e --- /dev/null +++ b/patches/server/0133-Fast-resume.patch @@ -0,0 +1,272 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 25 Jan 2024 01:16:49 +0800 +Subject: [PATCH] Fast resume + + +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +index c2eb3e8b019dbc0543a2308d7e88e324aa265cfe..22ffaeb54f618ccc8170d037eeaa4e25e798fc59 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +@@ -566,6 +566,56 @@ public final class ChunkHolderManager { + } + } + ++ // Leaves start - add custom ticket ++ public boolean addTicketAtLevelCustom(final Ticket ticket, final long chunk, final boolean lock) { ++ final long removeDelay = ticket.removeDelay; ++ if (ticket.getTicketLevel() > MAX_TICKET_LEVEL) { ++ return false; ++ } ++ ++ final int chunkX = CoordinateUtils.getChunkX(chunk); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunk); ++ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); ++ ++ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; ++ try { ++ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { ++ return SortedArraySet.create(4); ++ }); ++ ++ final int levelBefore = getTicketLevelAt(ticketsAtChunk); ++ final Ticket current = (Ticket)ticketsAtChunk.replace(ticket); ++ final int levelAfter = getTicketLevelAt(ticketsAtChunk); ++ ++ if (current != ticket) { ++ final long oldRemoveDelay = current.removeDelay; ++ if (removeDelay != oldRemoveDelay) { ++ if (oldRemoveDelay != NO_TIMEOUT_MARKER && removeDelay == NO_TIMEOUT_MARKER) { ++ this.removeExpireCount(chunkX, chunkZ); ++ } else if (oldRemoveDelay == NO_TIMEOUT_MARKER) { ++ // since old != new, we have that NO_TIMEOUT_MARKER != new ++ this.addExpireCount(chunkX, chunkZ); ++ } ++ } ++ } else { ++ if (removeDelay != NO_TIMEOUT_MARKER) { ++ this.addExpireCount(chunkX, chunkZ); ++ } ++ } ++ ++ if (levelBefore != levelAfter) { ++ this.updateTicketLevel(chunk, levelAfter); ++ } ++ ++ return current == ticket; ++ } finally { ++ if (ticketLock != null) { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ } ++ // Leaves end - add custom ticket ++ + public boolean removeTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, final T identifier) { + return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fc241a006ecddbef4ff563e2249e2a2800b9bf03..0d1da2e7d825643018e8d3a6425ff5347edfb404 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -663,6 +663,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> NEED_SAVED = Set.of(TicketType.PLAYER, TicketType.PORTAL, TicketType.ENTITY_LOAD, TicketType.POI_LOAD); ++ ++ public static void tryToLoadTickets() { ++ if (!LeavesConfig.fastResume) { ++ return; ++ } ++ ++ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("chunk_tickets.leaves.json").toFile(); ++ if (file.isFile()) { ++ try (BufferedReader bfr = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { ++ JsonObject json = new Gson().fromJson(bfr, JsonObject.class); ++ loadSavedChunkTickets(json); ++ file.delete(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ } ++ ++ public static void tryToSaveTickets() { ++ if (!LeavesConfig.fastResume) { ++ return; ++ } ++ ++ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("chunk_tickets.leaves.json").toFile(); ++ if (!file.isFile()) { ++ try { ++ file.createNewFile(); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { ++ bfw.write(new Gson().toJson(getSavedChunkTickets())); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ public static void loadSavedChunkTickets(JsonObject json) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ for (String worldKey : json.keySet()) { ++ ServerLevel level = server.getLevel(ResourceKey.create(Registries.DIMENSION, new ResourceLocation(worldKey))); ++ if (level == null) { ++ continue; ++ } ++ ++ DistanceManager chunkDistanceManager = level.getChunkSource().chunkMap.distanceManager; ++ for (JsonElement chunkElement : json.get(worldKey).getAsJsonArray()) { ++ JsonObject chunkJson = (JsonObject) chunkElement; ++ long chunkKey = chunkJson.get("key").getAsLong(); ++ ++ for (JsonElement ticketElement : chunkJson.get("tickets").getAsJsonArray()) { ++ Ticket ticket = tickFormJson((JsonObject) ticketElement); ++ chunkDistanceManager.getChunkHolderManager().addTicketAtLevelCustom(ticket, chunkKey, true); ++ } ++ } ++ } ++ } ++ ++ public static JsonObject getSavedChunkTickets() { ++ JsonObject json = new JsonObject(); ++ ++ for (ServerLevel level : MinecraftServer.getServer().getAllLevels()) { ++ JsonArray levelArray = new JsonArray(); ++ DistanceManager chunkDistanceManager = level.getChunkSource().chunkMap.distanceManager; ++ ++ for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { ++ long chunkKey = chunkTickets.getLongKey(); ++ JsonArray ticketArray = new JsonArray(); ++ SortedArraySet> tickets = chunkTickets.getValue(); ++ ++ for (Ticket ticket : tickets) { ++ if (!NEED_SAVED.contains(ticket.getType())) { ++ continue; ++ } ++ ++ ticketArray.add(ticketToJson(ticket)); ++ } ++ ++ if (!ticketArray.isEmpty()) { ++ JsonObject chunkJson = new JsonObject(); ++ chunkJson.addProperty("key", chunkKey); ++ chunkJson.add("tickets", ticketArray); ++ levelArray.add(chunkJson); ++ } ++ } ++ ++ if (!levelArray.isEmpty()) { ++ json.add(level.dimension().location().toString(), levelArray); ++ } ++ } ++ ++ return json; ++ } ++ ++ private static JsonObject ticketToJson(Ticket ticket) { ++ JsonObject json = new JsonObject(); ++ json.addProperty("type", ticket.getType().toString()); ++ json.addProperty("ticketLevel", ticket.getTicketLevel()); ++ json.addProperty("removeDelay", ticket.removeDelay); ++ if (ticket.key instanceof BlockPos pos) { ++ json.addProperty("key", pos.asLong()); ++ } else if (ticket.key instanceof ChunkPos pos) { ++ json.addProperty("key", pos.toLong()); ++ } else if (ticket.key instanceof Long l) { ++ json.addProperty("key", l); ++ } ++ return json; ++ } ++ ++ private static Ticket tickFormJson(JsonObject json) { ++ TicketType ticketType = null; ++ Object key = null; ++ switch (json.get("type").getAsString()) { ++ case "player" -> { ++ ticketType = TicketType.PLAYER; ++ key = new ChunkPos(json.get("key").getAsLong()); ++ } ++ case "portal" -> { ++ ticketType = TicketType.PORTAL; ++ key = BlockPos.of(json.get("key").getAsLong()); ++ } ++ case "entity_load" -> { ++ ticketType = TicketType.ENTITY_LOAD; ++ key = json.get("key").getAsLong(); ++ } ++ case "poi_load" -> { ++ ticketType = TicketType.POI_LOAD; ++ key = json.get("key").getAsLong(); ++ } ++ case "region_player_ticket" -> { ++ ticketType = RegionizedPlayerChunkLoader.REGION_PLAYER_TICKET; ++ key = json.get("key").getAsLong(); ++ } ++ } ++ ++ if (ticketType == null) { ++ throw new IllegalArgumentException("???"); ++ } ++ ++ int ticketLevel = json.get("ticketLevel").getAsInt(); ++ long removeDelay = json.get("removeDelay").getAsLong(); ++ ++ return new Ticket((TicketType) ticketType, ticketLevel, (T) key, removeDelay); ++ } ++}