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/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index 58d3d1a47e9f2423c467bb329c2d5f4b58a8b5ef..ea1ffe6b5e49ccf2b472829ed97e977b4938f3a5 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -582,6 +582,49 @@ public final class ChunkHolderManager { } } + // Leaves start - add custom ticket + public void addTicketAtLevelCustom(final Ticket ticket, final long chunk, final boolean lock) { + final long removeDelay = ticket.moonrise$getRemoveDelay(); + + final int chunkX = CoordinateUtils.getChunkX(chunk); + final int chunkZ = CoordinateUtils.getChunkZ(chunk); + + final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; + try { + final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (final long keyInMap) -> SortedArraySet.create(4)); + + final int levelBefore = getTicketLevelAt(ticketsAtChunk); + final Ticket current = (Ticket)((ChunkSystemSortedArraySet>)ticketsAtChunk).moonrise$replace(ticket); + final int levelAfter = getTicketLevelAt(ticketsAtChunk); + + if (current != ticket) { + final long oldRemoveDelay = ((ChunkSystemTicket) current).moonrise$getRemoveDelay(); + 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); + } + + } 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 0c43170b31363beca32407bddeee33ef9404d09a..3b4d1bd023904ead8b340021acd1d74b5aa53a87 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -749,6 +749,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> NEED_SAVED = Set.of(TicketType.PLAYER, TicketType.PORTAL, RegionizedPlayerChunkLoader.PLAYER_TICKET); + + 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); + if (!file.delete()) { + throw new IOException(); + } + } catch (IOException e) { + LeavesLogger.LOGGER.severe("Failed to load saved chunk tickets file", e); + } + } + } + + public static void tryToSaveTickets() { + if (!LeavesConfig.fastResume) { + return; + } + + File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("chunk_tickets.leaves.json").toFile(); + try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { + bfw.write(new Gson().toJson(getSavedChunkTickets())); + } catch (IOException e) { + LeavesLogger.LOGGER.severe("Failed to save chunk tickets file", e); + } + } + + public static void loadSavedChunkTickets(JsonObject json) { + MinecraftServer server = MinecraftServer.getServer(); + for (String worldKey : json.keySet()) { + ResourceLocation dimensionKey = ResourceLocation.tryParse(worldKey); + if (dimensionKey == null) { + continue; + } + + ServerLevel level = server.getLevel(ResourceKey.create(Registries.DIMENSION, dimensionKey)); + 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.moonrise$getRemoveDelay()); + 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 "chunk_system:player_ticket" -> { + ticketType = RegionizedPlayerChunkLoader.PLAYER_TICKET; + key = json.get("key").getAsLong(); + } + } + + if (ticketType == null) { + throw new IllegalArgumentException("Cant convert " + json.get("type").getAsString() + ", report it ???"); + } + + int ticketLevel = json.get("ticketLevel").getAsInt(); + long removeDelay = json.get("removeDelay").getAsLong(); + @SuppressWarnings("unchecked") + Ticket ticket = new Ticket<>((TicketType) ticketType, ticketLevel, (T) key); + ticket.moonrise$setRemoveDelay(removeDelay); + + return ticket; + } +}