-
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fe2d9ed
commit 2ab7c12
Showing
2 changed files
with
277 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||
From: violetc <[email protected]> | ||
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 <T> boolean addTicketAtLevelCustom(final Ticket<T> 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<Ticket<?>> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { | ||
+ return SortedArraySet.create(4); | ||
+ }); | ||
+ | ||
+ final int levelBefore = getTicketLevelAt(ticketsAtChunk); | ||
+ final Ticket<T> current = (Ticket<T>)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 <T> boolean removeTicketAtLevel(final TicketType<T> 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<TickTa | ||
} | ||
// Paper end - Configurable player collision | ||
|
||
+ top.leavesmc.leaves.util.TicketHelper.tryToLoadTickets(); // Leaves - load ticket | ||
top.leavesmc.leaves.bot.ServerBot.loadAllBot(); // Leaves - load resident bot | ||
|
||
this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); | ||
@@ -939,6 +940,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa | ||
// CraftBukkit end | ||
|
||
public void stopServer() { | ||
+ top.leavesmc.leaves.util.TicketHelper.tryToSaveTickets(); // Leaves - save ticket | ||
// CraftBukkit start - prevent double stopping on multiple threads | ||
synchronized(this.stopLock) { | ||
if (this.hasStopped) return; | ||
diff --git a/src/main/java/top/leavesmc/leaves/util/TicketHelper.java b/src/main/java/top/leavesmc/leaves/util/TicketHelper.java | ||
new file mode 100644 | ||
index 0000000000000000000000000000000000000000..9ab241f6a3d7d57bb6a8cdc222021fd0c2b499e5 | ||
--- /dev/null | ||
+++ b/src/main/java/top/leavesmc/leaves/util/TicketHelper.java | ||
@@ -0,0 +1,179 @@ | ||
+package top.leavesmc.leaves.util; | ||
+ | ||
+import com.google.gson.Gson; | ||
+import com.google.gson.JsonArray; | ||
+import com.google.gson.JsonElement; | ||
+import com.google.gson.JsonObject; | ||
+import io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader; | ||
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; | ||
+import net.minecraft.core.BlockPos; | ||
+import net.minecraft.core.registries.Registries; | ||
+import net.minecraft.resources.ResourceKey; | ||
+import net.minecraft.resources.ResourceLocation; | ||
+import net.minecraft.server.MinecraftServer; | ||
+import net.minecraft.server.level.DistanceManager; | ||
+import net.minecraft.server.level.ServerLevel; | ||
+import net.minecraft.server.level.Ticket; | ||
+import net.minecraft.server.level.TicketType; | ||
+import net.minecraft.util.SortedArraySet; | ||
+import net.minecraft.world.level.ChunkPos; | ||
+import net.minecraft.world.level.storage.LevelResource; | ||
+import top.leavesmc.leaves.LeavesConfig; | ||
+ | ||
+import java.io.BufferedReader; | ||
+import java.io.BufferedWriter; | ||
+import java.io.File; | ||
+import java.io.IOException; | ||
+import java.nio.charset.StandardCharsets; | ||
+import java.nio.file.Files; | ||
+import java.util.Set; | ||
+ | ||
+public class TicketHelper { | ||
+ | ||
+ private static final Set<TicketType<?>> 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<SortedArraySet<Ticket<?>>> chunkTickets : chunkDistanceManager.getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { | ||
+ long chunkKey = chunkTickets.getLongKey(); | ||
+ JsonArray ticketArray = new JsonArray(); | ||
+ SortedArraySet<Ticket<?>> 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 <T> Ticket<T> 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<T>((TicketType<T>) ticketType, ticketLevel, (T) key, removeDelay); | ||
+ } | ||
+} |