From feb584360bda09ff00740a2dbfbe5873dcadae4b Mon Sep 17 00:00:00 2001 From: Trigary Date: Sat, 22 Aug 2020 14:45:17 +0200 Subject: [PATCH 1/2] NamespacedKey based plugin chunk tickets --- ...spacedKey-based-plugin-chunk-tickets.patch | 304 ++++++++++++++++++ ...spacedKey-based-plugin-chunk-tickets.patch | 285 ++++++++++++++++ 2 files changed, 589 insertions(+) create mode 100644 Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch create mode 100644 Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch diff --git a/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch b/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch new file mode 100644 index 000000000000..86d978b6071a --- /dev/null +++ b/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch @@ -0,0 +1,304 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 22 Aug 2020 14:41:43 +0200 +Subject: [PATCH] NamespacedKey based plugin chunk tickets + + +diff --git a/src/main/java/io/papermc/paper/TickingLevel.java b/src/main/java/io/papermc/paper/TickingLevel.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e0ffc541981caae5ca031ff7e5b2247e2af030c5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/TickingLevel.java +@@ -0,0 +1,57 @@ ++package io.papermc.paper; ++ ++import org.apache.commons.lang.Validate; ++import org.bukkit.Chunk; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Represents what kind of ticking is done on the {@link Chunk}. ++ * The important values have dedicated cached instances, eg. {@link #ENTITY}. ++ * Read their own documentation for more information. ++ */ ++public enum TickingLevel { ++ ++ /** ++ * Both blocks and entities get ticked in this level. ++ */ ++ ENTITY, ++ ++ /** ++ * Blocks get ticked in this level, but entities do not. ++ */ ++ BLOCK, ++ ++ /** ++ * Nothing gets ticked in this level, but the {@link Chunk} stays loaded. ++ */ ++ NONE; ++ ++ /** ++ * Gets whether this ticking level does at least as much as the specified one. ++ * Examples: ++ * ++ * ++ * @param other the ticking level to test ++ * @return {@code true} if the current instance does at least everything ++ * the other one does, {@code false} otherwise ++ */ ++ @Contract(pure = true) ++ public boolean includes(@NotNull TickingLevel other) { ++ Validate.notNull(other, "Ticking level cannot be null"); ++ switch (this) { ++ case ENTITY: ++ return true; ++ case BLOCK: ++ return other != ENTITY; ++ case NONE: ++ return other == NONE; ++ default: ++ throw new AssertionError("Unhandled case: " + this); ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java +index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..eea7813b78b3c53f7729793730fedd9269646f2c 100644 +--- a/src/main/java/org/bukkit/Chunk.java ++++ b/src/main/java/org/bukkit/Chunk.java +@@ -240,6 +240,78 @@ public interface Chunk extends PersistentDataHolder { + @NotNull + Collection getPluginChunkTickets(); + ++ // Paper start ++ /** ++ * Adds a {@link NamespacedKey} based plugin chunk ticket ++ * with {@link io.papermc.paper.TickingLevel#ENTITY}. ++ * If a ticket with a different level is already present, it will be removed. ++ * ++ * @param key the identifier of the ticket ++ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * or {@code null} if no ticket was present with the specified key ++ * @see World#addKeyedPluginChunkTicket(int, int, NamespacedKey) ++ */ ++ @org.jetbrains.annotations.Nullable ++ default io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key) { ++ return getWorld().addKeyedPluginChunkTicket(getX(), getZ(), key); ++ } ++ ++ /** ++ * Adds a {@link NamespacedKey} based plugin chunk ticket ++ * with the specified {@link io.papermc.paper.TickingLevel}. ++ * If a ticket with a different level is already present, it will be removed. ++ * ++ * @param key the identifier of the ticket ++ * @param level the level of the ticket ++ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * or {@code null} if no ticket was present with the specified key ++ * @see World#addKeyedPluginChunkTicket(int, int, NamespacedKey, io.papermc.paper.TickingLevel) ++ */ ++ @org.jetbrains.annotations.Nullable ++ default io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key, @NotNull io.papermc.paper.TickingLevel level) { ++ return getWorld().addKeyedPluginChunkTicket(getX(), getZ(), key, level); ++ } ++ ++ /** ++ * Removes the {@link NamespacedKey} based plugin chunk ticket ++ * which has the specified key, if there is one. ++ * ++ * @param key the identifier of the tickets ++ * @return the {@link io.papermc.paper.TickingLevel} that was removed ++ * or {@code null} if no ticket was present with the specified key ++ * @see World#removeKeyedPluginChunkTicket(int, int, NamespacedKey) ++ */ ++ @org.jetbrains.annotations.Nullable ++ default io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(@NotNull NamespacedKey key) { ++ return getWorld().removeKeyedPluginChunkTicket(getX(), getZ(), key); ++ } ++ ++ /** ++ * Gets the {@link NamespacedKey} based plugin chunk ticket level ++ * with the specified key, if there is one. ++ * ++ * @param key the identifier of the tickets ++ * @return the level which is present with the specified key or {@code null} if there is none ++ * @see #getKeyedPluginChunkTicketLevel(NamespacedKey) ++ */ ++ @org.jetbrains.annotations.Nullable ++ default io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(@NotNull NamespacedKey key) { ++ return getWorld().getKeyedPluginChunkTicketLevel(getX(), getZ(), key); ++ } ++ ++ /** ++ * Gets the keys of the {@link NamespacedKey} based plugin chunk tickets ++ * which are present in this chunk. ++ * ++ * @return the keys which are present ++ * @see World#getKeyedPluginChunkTicketKeys(int, int) ++ */ ++ @NotNull ++ default Collection getKeyedPluginChunkTicketKeys() { ++ return getWorld().getKeyedPluginChunkTicketKeys(getX(), getZ()); ++ } ++ // Paper end ++ + /** + * Gets the amount of time in ticks that this chunk has been inhabited. + * +diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java +index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b039880ac95 100644 +--- a/src/main/java/org/bukkit/World.java ++++ b/src/main/java/org/bukkit/World.java +@@ -1115,6 +1115,147 @@ public interface World extends PluginMessageRecipient, Metadatable { + @NotNull + public Map> getPluginChunkTickets(); + ++ // Paper start ++ /** ++ * Adds a {@link NamespacedKey} based plugin chunk ticket ++ * to the specified chunk with {@link io.papermc.paper.TickingLevel#ENTITY}. ++ * If a ticket with a different level is already present, it will be removed. ++ * ++ * @param x X-coordinate of the chunk ++ * @param z Z-coordinate of the chunk ++ * @param key the identifier of the ticket ++ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * or {@code null} if no ticket was present with the specified key ++ * @see #addKeyedPluginChunkTicket(int, int, NamespacedKey, io.papermc.paper.TickingLevel) ++ */ ++ @Nullable ++ io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); ++ ++ /** ++ * Adds a {@link NamespacedKey} based plugin chunk ticket ++ * to the specified chunk with the specified {@link io.papermc.paper.TickingLevel}. ++ * If a ticket with a different level is already present, it will be removed. ++ * ++ * @param x X-coordinate of the chunk ++ * @param z Z-coordinate of the chunk ++ * @param key the identifier of the ticket ++ * @param level the level of the ticket ++ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * or {@code null} if no ticket was present with the specified key ++ */ ++ @Nullable ++ io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key, @NotNull io.papermc.paper.TickingLevel level); ++ ++ /** ++ * Removes the {@link NamespacedKey} based plugin chunk ticket ++ * from the specified chunk which has the specified key, if there is one. ++ * ++ * @param x X-coordinate of the chunk ++ * @param z Z-coordinate of the chunk ++ * @param key the identifier of the tickets ++ * @return the {@link io.papermc.paper.TickingLevel} that was removed ++ * or {@code null} if no ticket was present with the specified key ++ */ ++ @Nullable ++ io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); ++ ++ /** ++ * Gets the {@link NamespacedKey} based plugin chunk ticket level ++ * in the specified chunk with the specified key, if there is one. ++ * ++ * @param x X-coordinate of the chunk ++ * @param z Z-coordinate of the chunk ++ * @param key the identifier of the tickets ++ * @return the level which is present with the specified key or {@code null} if there is none ++ */ ++ @Nullable ++ io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, @NotNull NamespacedKey key); ++ ++ /** ++ * Gets the keys of the {@link NamespacedKey} based plugin chunk tickets ++ * which are present in the specified chunk. ++ * ++ * @param x X-coordinate of the chunk ++ * @param z Z-coordinate of the chunk ++ * @return the keys which are present ++ */ ++ @NotNull ++ Collection getKeyedPluginChunkTicketKeys(int x, int z); ++ ++ /** ++ * Removes all {@link NamespacedKey} based plugin chunk tickets ++ * from each chunk which has the specified plugin ++ * according to {@link NamespacedKey#getNamespace()}. ++ * ++ * @param plugin the plugin whose tickets to remove ++ */ ++ void removeKeyedPluginChunkTickets(@NotNull Plugin plugin); ++ ++ /** ++ * Removes all {@link NamespacedKey} based plugin chunk tickets ++ * from each chunk which has the specified key. ++ * ++ * @param key the identifier of the tickets which should be removed ++ */ ++ void removeKeyedPluginChunkTickets(@NotNull NamespacedKey key); ++ ++ /** ++ * Gets all chunk keys which have a {@link NamespacedKey} ++ * based plugin chunk ticket with the specified key. ++ * Keep in mind that these chunks might not be loaded yet: ++ * having a chunk ticket only guarantees that chunk loading has begun, ++ * not that the loading has already been completed. ++ * If only loaded chunks are needed, ++ * please refer to {@link #getKeyedPluginChunkTicketLoadedChunks(NamespacedKey)}. ++ * More information regarding chunk keys can be found ++ * in the documentation of {@link #getChunkAt(long)}. ++ * ++ * @return the chunk keys which have tickets with the specified key ++ */ ++ @NotNull ++ Collection getKeyedPluginChunkTicketChunks(@NotNull NamespacedKey key); ++ ++ /** ++ * Gets all loaded chunks which have a {@link NamespacedKey} ++ * based plugin chunk ticket with the specified key. ++ * Keep in mind that only chunks are included. ++ * If that's not desired, or if you would like to know more, ++ * please refer to {@link #getKeyedPluginChunkTicketChunks(NamespacedKey)}. ++ * ++ * @return the loaded chunks which have tickets with the specified key ++ */ ++ @NotNull ++ Collection getKeyedPluginChunkTicketLoadedChunks(@NotNull NamespacedKey key); ++ ++ /** ++ * Gets all {@link NamespacedKey} based plugin chunk tickets, ++ * with chunk keys mapped to the ticket keys they contain. ++ * Keep in mind that these chunks might not be loaded yet: ++ * having a chunk ticket only guarantees that chunk loading has begun, ++ * not that the loading has already been completed. ++ * If only loaded chunks are needed, ++ * please refer to {@link #getAllLoadedKeyedPluginChunkTickets()}. ++ * More information regarding chunk keys can be found ++ * in the documentation of {@link #getChunkAt(long)}. ++ * ++ * @return the chunks and their ticket keys ++ */ ++ @NotNull ++ com.google.common.collect.Multimap getAllKeyedPluginChunkTickets(); ++ ++ /** ++ * Gets all {@link NamespacedKey} based plugin chunk tickets whose chunks are loaded, ++ * with chunks mapped to the ticket keys they contain. ++ * Keep in mind that only chunks are included. ++ * If that's not desired, or if you would like to know more, ++ * please refer to {@link #getAllKeyedPluginChunkTickets()}. ++ * ++ * @return the chunks and their ticket keys ++ */ ++ @NotNull ++ com.google.common.collect.Multimap getAllLoadedKeyedPluginChunkTickets(); ++ // Paper end ++ + /** + * Drops an item at the specified {@link Location} + * diff --git a/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch b/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch new file mode 100644 index 000000000000..75d9350f7a01 --- /dev/null +++ b/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch @@ -0,0 +1,285 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 22 Aug 2020 14:43:26 +0200 +Subject: [PATCH] NamespacedKey based plugin chunk tickets + + +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index 3c7b225edbe23dc1959002293a6f8b816287b5a8..d733ff69d44a1ee10f3d7c41b92cd031dbccb035 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -464,6 +464,34 @@ public abstract class ChunkMapDistance { + } + // CraftBukkit end + ++ // Paper start ++ public int removeIfAllTicketsFor(TicketType ticketType, java.util.function.Predicate> predicate) { ++ // logic copied from removeAllTicketsFor ++ int count = 0; ++ for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext(); ) { ++ Entry>> entry = iterator.next(); ++ ArraySetSorted> tickets = entry.getValue(); ++ for (java.util.Iterator> ticketIterator = tickets.iterator(); ticketIterator.hasNext(); ) { ++ Ticket ticket = ticketIterator.next(); ++ //noinspection unchecked ++ if (ticket.getTicketType() == ticketType && predicate.test((Ticket) ticket)) { ++ count++; ++ ticketIterator.remove(); ++ } ++ } ++ ++ // copied from removeTicket ++ this.ticketLevelTracker.update(entry.getLongKey(), getLowestTicketLevel(tickets), false); ++ ++ // can't use entry after it's removed ++ if (tickets.isEmpty()) { ++ iterator.remove(); ++ } ++ } ++ return count; ++ } ++ // Paper end ++ + class a extends ChunkMap { + + public a() { +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index ff74be14512a947e81b62d53e616131ca7d7f609..2a6ef7270a2f5b6195edfa646150c32476239a51 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -5,6 +5,7 @@ import com.destroystokyo.paper.profile.CraftPlayerProfile; + import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import io.papermc.paper.TickingLevel; + import org.apache.commons.lang.exception.ExceptionUtils; + import com.google.gson.JsonArray; + import com.google.gson.JsonObject; +@@ -704,4 +705,31 @@ public final class MCUtil { + // TODO make sure the constant `33` is correct on future updates. See getChunkAt(int, int, ChunkStatus, boolean) + return 33 + ChunkStatus.getTicketLevelOffset(status); + } ++ ++ public static int getTicketLevelFrom(TickingLevel tickingLevel) { ++ switch (tickingLevel) { ++ case ENTITY: ++ return 31; ++ case BLOCK: ++ return 32; ++ case NONE: ++ return 33; ++ default: ++ throw new AssertionError("Unhandled case: " + tickingLevel); ++ } ++ } ++ ++ @Nullable ++ public static TickingLevel tryGetTickingLevelFrom(int ticketLevel) { ++ switch (ticketLevel) { ++ case 31: ++ return TickingLevel.ENTITY; ++ case 32: ++ return TickingLevel.BLOCK; ++ case 33: ++ return TickingLevel.NONE; ++ default: ++ return null; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java +index 5c789b25f1df2eae8ea8ceb4ba977ba336fe6d5e..2cb8991b8d2b46478f17388a80fefde1091f6245 100644 +--- a/src/main/java/net/minecraft/server/TicketType.java ++++ b/src/main/java/net/minecraft/server/TicketType.java +@@ -22,6 +22,7 @@ public class TicketType { + public static final TicketType UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1); + public static final TicketType PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit ++ public static final TicketType KEYED_PLUGIN_TICKET = a("keyed_plugin_ticket", Comparator.comparing(org.bukkit.NamespacedKey::getNamespace).thenComparing(org.bukkit.NamespacedKey::getKey)); // Paper + public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper + public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper + public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b71316cce3bdbf3485be456f0260c6b3463cff8e..ca4d6883385c5a2f357c7caba7291b772e0a751c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -670,6 +670,7 @@ public class CraftWorld implements World { + + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; + chunkDistanceManager.removeAllTicketsFor(TicketType.PLUGIN_TICKET, 31, plugin); // keep in-line with force loading, remove at level 31 ++ removeKeyedPluginChunkTickets(plugin); // Paper + } + + @Override +@@ -717,6 +718,171 @@ public class CraftWorld implements World { + return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); + } + ++ // Paper start ++ @Override ++ public io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { ++ return addKeyedPluginChunkTicket(x, z, key, io.papermc.paper.TickingLevel.ENTITY); ++ } ++ ++ @Override ++ public io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key, io.papermc.paper.TickingLevel level) { ++ Preconditions.checkArgument(key != null, "null key"); ++ Preconditions.checkArgument(level != null, "null level"); ++ ++ io.papermc.paper.TickingLevel previousLevel = getKeyedPluginChunkTicketLevel(x, z, key); ++ if (previousLevel == level) { ++ return level; //ticket already present at desired level -> do nothing ++ } ++ ++ if (previousLevel != null) { ++ //ticket with another level is present -> remove it first ++ // (only 1 level per chunk-key pair is allowed) ++ removeKeyedPluginChunkTicket(x, z, key); ++ } ++ ++ //finally actually add the ticket ++ int ticketLevel = MCUtil.getTicketLevelFrom(level); ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ manager.addTicketAtLevel(TicketType.KEYED_PLUGIN_TICKET, new ChunkCoordIntPair(x, z), ticketLevel, key); ++ return previousLevel; ++ } ++ ++ @Override ++ public io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { ++ Preconditions.checkArgument(key != null, "null key"); ++ ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ ChunkCoordIntPair coordPair = new ChunkCoordIntPair(x, z); ++ ++ for (io.papermc.paper.TickingLevel level : io.papermc.paper.TickingLevel.values()) { ++ int ticketLevel = MCUtil.getTicketLevelFrom(level); ++ if (manager.removeTicketAtLevel(TicketType.KEYED_PLUGIN_TICKET, coordPair, ticketLevel, key)) { ++ return level; ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, org.bukkit.NamespacedKey key) { ++ Preconditions.checkArgument(key != null, "null key"); ++ ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ Collection> tickets = manager.tickets.get(ChunkCoordIntPair.pair(x, z)); ++ if (tickets == null) { ++ return null; ++ } ++ ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.KEYED_PLUGIN_TICKET && key.equals(ticket.getObjectReason())) { ++ return MCUtil.tryGetTickingLevelFrom(ticket.getTicketLevel()); ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public Collection getKeyedPluginChunkTicketKeys(int x, int z) { ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ Collection> tickets = manager.tickets.get(ChunkCoordIntPair.pair(x, z)); ++ if (tickets == null) { ++ return Collections.emptyList(); ++ } ++ ++ com.google.common.collect.ImmutableSet.Builder ret = com.google.common.collect.ImmutableSet.builder(); ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.KEYED_PLUGIN_TICKET) { ++ ret.add((org.bukkit.NamespacedKey) ticket.getObjectReason()); ++ } ++ } ++ return ret.build(); ++ } ++ ++ @Override ++ public void removeKeyedPluginChunkTickets(Plugin plugin) { ++ Preconditions.checkArgument(plugin != null, "null plugin"); ++ ++ String namespace = new org.bukkit.NamespacedKey(plugin, "ignored").getNamespace(); ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ manager.removeIfAllTicketsFor(TicketType.KEYED_PLUGIN_TICKET, ++ ticket -> namespace.equals(ticket.getObjectReason().getNamespace())); ++ } ++ ++ @Override ++ public void removeKeyedPluginChunkTickets(org.bukkit.NamespacedKey key) { ++ Preconditions.checkArgument(key != null, "null key"); ++ ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ manager.removeIfAllTicketsFor(TicketType.KEYED_PLUGIN_TICKET, ++ ticket -> key.equals(ticket.getObjectReason())); ++ } ++ ++ @Override ++ public Collection getKeyedPluginChunkTicketChunks(org.bukkit.NamespacedKey key) { ++ Preconditions.checkArgument(key != null, "null key"); ++ ++ com.google.common.collect.ImmutableSet.Builder ret = com.google.common.collect.ImmutableSet.builder(); ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ ++ for (Long2ObjectMap.Entry>> chunkTickets : manager.tickets.long2ObjectEntrySet()) { ++ for (Ticket ticket : chunkTickets.getValue()) { ++ if (ticket.getTicketType() == TicketType.KEYED_PLUGIN_TICKET && key.equals(ticket.getObjectReason())) { ++ ret.add(chunkTickets.getLongKey()); ++ break; ++ } ++ } ++ } ++ return ret.build(); ++ } ++ ++ @Override ++ public Collection getKeyedPluginChunkTicketLoadedChunks(org.bukkit.NamespacedKey key) { ++ com.google.common.collect.ImmutableSet.Builder ret = com.google.common.collect.ImmutableSet.builder(); ++ for (long chunkKey : getKeyedPluginChunkTicketChunks(key)) { ++ int x = MCUtil.getCoordinateX(chunkKey); ++ int z = MCUtil.getCoordinateZ(chunkKey); ++ net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(x, z); ++ if (chunk != null) { ++ ret.add(chunk.bukkitChunk); ++ } ++ } ++ return ret.build(); ++ } ++ ++ @Override ++ public com.google.common.collect.Multimap getAllKeyedPluginChunkTickets() { ++ com.google.common.collect.Multimap ret = com.google.common.collect.HashMultimap.create(); ++ ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ ++ for (Long2ObjectMap.Entry>> chunkTickets : manager.tickets.long2ObjectEntrySet()) { ++ long chunkKey = chunkTickets.getLongKey(); ++ ++ for (Ticket ticket : chunkTickets.getValue()) { ++ if (ticket.getTicketType() == TicketType.KEYED_PLUGIN_TICKET) { ++ ret.put(chunkKey, (org.bukkit.NamespacedKey) ticket.getObjectReason()); ++ } ++ } ++ } ++ return ret; ++ } ++ ++ @Override ++ public com.google.common.collect.Multimap getAllLoadedKeyedPluginChunkTickets() { ++ com.google.common.collect.Multimap ret = com.google.common.collect.HashMultimap.create(); ++ com.google.common.collect.Multimap all = getAllKeyedPluginChunkTickets(); ++ ++ for (long chunkKey : all.keySet()) { ++ int x = MCUtil.getCoordinateX(chunkKey); ++ int z = MCUtil.getCoordinateZ(chunkKey); ++ net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(x, z); ++ if (chunk != null) { ++ ret.putAll(chunk.bukkitChunk, all.get(chunkKey)); ++ } ++ } ++ return ret; ++ } ++ // Paper end ++ + @Override + public boolean isChunkForceLoaded(int x, int z) { + return getHandle().getForceLoadedChunks().contains(ChunkCoordIntPair.pair(x, z)); From 4ad683cfd5be2e96ea86f907557701f7f2a84fb7 Mon Sep 17 00:00:00 2001 From: Trigary Date: Wed, 10 Feb 2021 22:56:14 +0100 Subject: [PATCH 2/2] improve javadocs, move removeKeyedPluginChunkTickets(Plugin) call to SimplePluginManager --- ...spacedKey-based-plugin-chunk-tickets.patch | 130 +++++++++++------- ...spacedKey-based-plugin-chunk-tickets.patch | 34 ++--- 2 files changed, 94 insertions(+), 70 deletions(-) diff --git a/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch b/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch index 86d978b6071a..ffbb2af9a419 100644 --- a/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch +++ b/Spigot-API-Patches/0242-NamespacedKey-based-plugin-chunk-tickets.patch @@ -4,13 +4,13 @@ Date: Sat, 22 Aug 2020 14:41:43 +0200 Subject: [PATCH] NamespacedKey based plugin chunk tickets -diff --git a/src/main/java/io/papermc/paper/TickingLevel.java b/src/main/java/io/papermc/paper/TickingLevel.java +diff --git a/src/main/java/io/papermc/paper/world/TickingLevel.java b/src/main/java/io/papermc/paper/world/TickingLevel.java new file mode 100644 -index 0000000000000000000000000000000000000000..e0ffc541981caae5ca031ff7e5b2247e2af030c5 +index 0000000000000000000000000000000000000000..64b77f2c7ca920a44d6628b3edacbaeb3f727b1f --- /dev/null -+++ b/src/main/java/io/papermc/paper/TickingLevel.java -@@ -0,0 +1,57 @@ -+package io.papermc.paper; ++++ b/src/main/java/io/papermc/paper/world/TickingLevel.java +@@ -0,0 +1,55 @@ ++package io.papermc.paper.world; + +import org.apache.commons.lang.Validate; +import org.bukkit.Chunk; @@ -19,8 +19,6 @@ index 0000000000000000000000000000000000000000..e0ffc541981caae5ca031ff7e5b2247e + +/** + * Represents what kind of ticking is done on the {@link Chunk}. -+ * The important values have dedicated cached instances, eg. {@link #ENTITY}. -+ * Read their own documentation for more information. + */ +public enum TickingLevel { + @@ -41,7 +39,7 @@ index 0000000000000000000000000000000000000000..e0ffc541981caae5ca031ff7e5b2247e + + /** + * Gets whether this ticking level does at least as much as the specified one. -+ * Examples: ++ * Examples (all of these expressions evaluate to {@code true}): + *
    + *
  • {@code BLOCK.includes(ENTITY) == false}
  • + *
  • {@code BLOCK.includes(BLOCK) == true}
  • @@ -68,42 +66,50 @@ index 0000000000000000000000000000000000000000..e0ffc541981caae5ca031ff7e5b2247e + } +} diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java -index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..eea7813b78b3c53f7729793730fedd9269646f2c 100644 +index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..94c36228346a1b79529e2308f4654c80db4d6614 100644 --- a/src/main/java/org/bukkit/Chunk.java +++ b/src/main/java/org/bukkit/Chunk.java -@@ -240,6 +240,78 @@ public interface Chunk extends PersistentDataHolder { +@@ -8,6 +8,7 @@ import org.bukkit.entity.Entity; + import org.bukkit.persistence.PersistentDataHolder; + import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.NotNull; ++import io.papermc.paper.world.TickingLevel; // Paper + + /** + * Represents a chunk of blocks +@@ -240,6 +241,78 @@ public interface Chunk extends PersistentDataHolder { @NotNull Collection getPluginChunkTickets(); + // Paper start + /** + * Adds a {@link NamespacedKey} based plugin chunk ticket -+ * with {@link io.papermc.paper.TickingLevel#ENTITY}. ++ * with {@link TickingLevel#ENTITY}. + * If a ticket with a different level is already present, it will be removed. + * + * @param key the identifier of the ticket -+ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * @return the {@link TickingLevel} that was overwritten + * or {@code null} if no ticket was present with the specified key + * @see World#addKeyedPluginChunkTicket(int, int, NamespacedKey) + */ + @org.jetbrains.annotations.Nullable -+ default io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key) { ++ default TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key) { + return getWorld().addKeyedPluginChunkTicket(getX(), getZ(), key); + } + + /** + * Adds a {@link NamespacedKey} based plugin chunk ticket -+ * with the specified {@link io.papermc.paper.TickingLevel}. ++ * with the specified {@link TickingLevel}. + * If a ticket with a different level is already present, it will be removed. + * + * @param key the identifier of the ticket + * @param level the level of the ticket -+ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * @return the {@link TickingLevel} that was overwritten + * or {@code null} if no ticket was present with the specified key -+ * @see World#addKeyedPluginChunkTicket(int, int, NamespacedKey, io.papermc.paper.TickingLevel) ++ * @see World#addKeyedPluginChunkTicket(int, int, NamespacedKey, TickingLevel) + */ + @org.jetbrains.annotations.Nullable -+ default io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key, @NotNull io.papermc.paper.TickingLevel level) { ++ default TickingLevel addKeyedPluginChunkTicket(@NotNull NamespacedKey key, @NotNull TickingLevel level) { + return getWorld().addKeyedPluginChunkTicket(getX(), getZ(), key, level); + } + @@ -112,12 +118,12 @@ index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..eea7813b78b3c53f7729793730fedd92 + * which has the specified key, if there is one. + * + * @param key the identifier of the tickets -+ * @return the {@link io.papermc.paper.TickingLevel} that was removed ++ * @return the {@link TickingLevel} that was removed + * or {@code null} if no ticket was present with the specified key + * @see World#removeKeyedPluginChunkTicket(int, int, NamespacedKey) + */ + @org.jetbrains.annotations.Nullable -+ default io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(@NotNull NamespacedKey key) { ++ default TickingLevel removeKeyedPluginChunkTicket(@NotNull NamespacedKey key) { + return getWorld().removeKeyedPluginChunkTicket(getX(), getZ(), key); + } + @@ -130,7 +136,7 @@ index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..eea7813b78b3c53f7729793730fedd92 + * @see #getKeyedPluginChunkTicketLevel(NamespacedKey) + */ + @org.jetbrains.annotations.Nullable -+ default io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(@NotNull NamespacedKey key) { ++ default TickingLevel getKeyedPluginChunkTicketLevel(@NotNull NamespacedKey key) { + return getWorld().getKeyedPluginChunkTicketLevel(getX(), getZ(), key); + } + @@ -151,43 +157,53 @@ index b4ef6297f78d1f0c216e718024a21e6aa07cd1c6..eea7813b78b3c53f7729793730fedd92 * Gets the amount of time in ticks that this chunk has been inhabited. * diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b039880ac95 100644 +index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..b94ea04f2ca9474e32a9cdeacd058310775ca884 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -1115,6 +1115,147 @@ public interface World extends PluginMessageRecipient, Metadatable { +@@ -39,6 +39,7 @@ import org.bukkit.util.Vector; + import org.jetbrains.annotations.Contract; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; ++import io.papermc.paper.world.TickingLevel; // Paper + + /** + * Represents a world, which may contain entities, chunks and blocks +@@ -1115,6 +1116,145 @@ public interface World extends PluginMessageRecipient, Metadatable { @NotNull public Map> getPluginChunkTickets(); + // Paper start + /** + * Adds a {@link NamespacedKey} based plugin chunk ticket -+ * to the specified chunk with {@link io.papermc.paper.TickingLevel#ENTITY}. ++ * to the specified chunk with {@link TickingLevel#ENTITY}. + * If a ticket with a different level is already present, it will be removed. ++ * Asynchronously loads the specified {@link Chunk} if it isn't loaded. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param key the identifier of the ticket -+ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * @return the {@link TickingLevel} that was overwritten + * or {@code null} if no ticket was present with the specified key -+ * @see #addKeyedPluginChunkTicket(int, int, NamespacedKey, io.papermc.paper.TickingLevel) ++ * @see #addKeyedPluginChunkTicket(int, int, NamespacedKey, TickingLevel) + */ + @Nullable -+ io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); ++ TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); + + /** + * Adds a {@link NamespacedKey} based plugin chunk ticket -+ * to the specified chunk with the specified {@link io.papermc.paper.TickingLevel}. ++ * to the specified chunk with the specified {@link TickingLevel}. + * If a ticket with a different level is already present, it will be removed. ++ * Asynchronously loads the specified {@link Chunk} if it isn't loaded. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param key the identifier of the ticket + * @param level the level of the ticket -+ * @return the {@link io.papermc.paper.TickingLevel} that was overwritten ++ * @return the {@link TickingLevel} that was overwritten + * or {@code null} if no ticket was present with the specified key + */ + @Nullable -+ io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key, @NotNull io.papermc.paper.TickingLevel level); ++ TickingLevel addKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key, @NotNull TickingLevel level); + + /** + * Removes the {@link NamespacedKey} based plugin chunk ticket @@ -196,11 +212,11 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param key the identifier of the tickets -+ * @return the {@link io.papermc.paper.TickingLevel} that was removed ++ * @return the {@link TickingLevel} that was removed + * or {@code null} if no ticket was present with the specified key + */ + @Nullable -+ io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); ++ TickingLevel removeKeyedPluginChunkTicket(int x, int z, @NotNull NamespacedKey key); + + /** + * Gets the {@link NamespacedKey} based plugin chunk ticket level @@ -212,7 +228,7 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + * @return the level which is present with the specified key or {@code null} if there is none + */ + @Nullable -+ io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, @NotNull NamespacedKey key); ++ TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, @NotNull NamespacedKey key); + + /** + * Gets the keys of the {@link NamespacedKey} based plugin chunk tickets @@ -227,7 +243,7 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + + /** + * Removes all {@link NamespacedKey} based plugin chunk tickets -+ * from each chunk which has the specified plugin ++ * (from each chunk in this world) which are owned by the specified plugin + * according to {@link NamespacedKey#getNamespace()}. + * + * @param plugin the plugin whose tickets to remove @@ -236,22 +252,20 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + + /** + * Removes all {@link NamespacedKey} based plugin chunk tickets -+ * from each chunk which has the specified key. ++ * (from each chunk in this world) which have the specified key. + * + * @param key the identifier of the tickets which should be removed + */ + void removeKeyedPluginChunkTickets(@NotNull NamespacedKey key); + + /** -+ * Gets all chunk keys which have a {@link NamespacedKey} -+ * based plugin chunk ticket with the specified key. ++ * Gets all chunk keys (which can be used in eg. {@link World#getChunkAt(long)}) ++ * which have a {@link NamespacedKey} based plugin chunk ticket with the specified key. + * Keep in mind that these chunks might not be loaded yet: + * having a chunk ticket only guarantees that chunk loading has begun, + * not that the loading has already been completed. -+ * If only loaded chunks are needed, -+ * please refer to {@link #getKeyedPluginChunkTicketLoadedChunks(NamespacedKey)}. -+ * More information regarding chunk keys can be found -+ * in the documentation of {@link #getChunkAt(long)}. ++ * If only loaded chunks are needed, please use ++ * {@link #getKeyedPluginChunkTicketLoadedChunks(NamespacedKey)} instead. + * + * @return the chunk keys which have tickets with the specified key + */ @@ -261,9 +275,9 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + /** + * Gets all loaded chunks which have a {@link NamespacedKey} + * based plugin chunk ticket with the specified key. -+ * Keep in mind that only chunks are included. -+ * If that's not desired, or if you would like to know more, -+ * please refer to {@link #getKeyedPluginChunkTicketChunks(NamespacedKey)}. ++ * Keep in mind that only loaded chunks are included. ++ * If that's not desired, please use ++ * {@link #getKeyedPluginChunkTicketChunks(NamespacedKey)} instead. + * + * @return the loaded chunks which have tickets with the specified key + */ @@ -271,27 +285,25 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 + Collection getKeyedPluginChunkTicketLoadedChunks(@NotNull NamespacedKey key); + + /** -+ * Gets all {@link NamespacedKey} based plugin chunk tickets, -+ * with chunk keys mapped to the ticket keys they contain. ++ * Gets all {@link NamespacedKey} based plugin chunk tickets ++ * and their chunk keys (which can be used in eg. {@link World#getChunkAt(long)}). ++ * The chunk keys point to the ticket keys they contain. + * Keep in mind that these chunks might not be loaded yet: + * having a chunk ticket only guarantees that chunk loading has begun, + * not that the loading has already been completed. -+ * If only loaded chunks are needed, -+ * please refer to {@link #getAllLoadedKeyedPluginChunkTickets()}. -+ * More information regarding chunk keys can be found -+ * in the documentation of {@link #getChunkAt(long)}. ++ * If only loaded chunks are needed, please use ++ * {@link #getAllLoadedKeyedPluginChunkTickets()} instead. + * -+ * @return the chunks and their ticket keys ++ * @return the chunk keys and their ticket keys + */ + @NotNull + com.google.common.collect.Multimap getAllKeyedPluginChunkTickets(); + + /** + * Gets all {@link NamespacedKey} based plugin chunk tickets whose chunks are loaded, -+ * with chunks mapped to the ticket keys they contain. ++ * and their chunks. The chunks point to the ticket keys they contain. + * Keep in mind that only chunks are included. -+ * If that's not desired, or if you would like to know more, -+ * please refer to {@link #getAllKeyedPluginChunkTickets()}. ++ * If that's not desired, please use {@link #getAllKeyedPluginChunkTickets()} instead. + * + * @return the chunks and their ticket keys + */ @@ -302,3 +314,15 @@ index e6d2abf284c103a8bcddd8b4f9cb34d86a4f2fa6..a81a089743f0407ff7a6952d4afa8b03 /** * Drops an item at the specified {@link Location} * +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 26685f59b235ea5b4c4fb7ae21acb5149edaa2b3..4db22c112a3e81ca5d118403f3d6e502244ace94 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -551,6 +551,7 @@ public final class SimplePluginManager implements PluginManager { + try { + for (World world : server.getWorlds()) { + world.removePluginChunkTickets(plugin); ++ world.removeKeyedPluginChunkTickets(plugin); // Paper + } + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while removing chunk tickets for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); diff --git a/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch b/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch index 75d9350f7a01..40fce7a568df 100644 --- a/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch +++ b/Spigot-Server-Patches/0615-NamespacedKey-based-plugin-chunk-tickets.patch @@ -44,14 +44,14 @@ index 3c7b225edbe23dc1959002293a6f8b816287b5a8..d733ff69d44a1ee10f3d7c41b92cd031 public a() { diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index ff74be14512a947e81b62d53e616131ca7d7f609..2a6ef7270a2f5b6195edfa646150c32476239a51 100644 +index ff74be14512a947e81b62d53e616131ca7d7f609..736d00761b42bbd982707e7dc9b1d7c77eeb99d5 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -5,6 +5,7 @@ import com.destroystokyo.paper.profile.CraftPlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile; import com.google.common.util.concurrent.ThreadFactoryBuilder; import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; -+import io.papermc.paper.TickingLevel; ++import io.papermc.paper.world.TickingLevel; import org.apache.commons.lang.exception.ExceptionUtils; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -100,33 +100,33 @@ index 5c789b25f1df2eae8ea8ceb4ba977ba336fe6d5e..2cb8991b8d2b46478f17388a80fefde1 public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index b71316cce3bdbf3485be456f0260c6b3463cff8e..ca4d6883385c5a2f357c7caba7291b772e0a751c 100644 +index b71316cce3bdbf3485be456f0260c6b3463cff8e..88928a7c12c390c97614a467fb7478e6321129de 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -670,6 +670,7 @@ public class CraftWorld implements World { +@@ -267,6 +267,7 @@ import org.bukkit.util.BoundingBox; + import org.bukkit.util.Consumer; + import org.bukkit.util.RayTraceResult; + import org.bukkit.util.Vector; ++import io.papermc.paper.world.TickingLevel; // Paper - ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; - chunkDistanceManager.removeAllTicketsFor(TicketType.PLUGIN_TICKET, 31, plugin); // keep in-line with force loading, remove at level 31 -+ removeKeyedPluginChunkTickets(plugin); // Paper - } - - @Override + public class CraftWorld implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; @@ -717,6 +718,171 @@ public class CraftWorld implements World { return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); } + // Paper start + @Override -+ public io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { -+ return addKeyedPluginChunkTicket(x, z, key, io.papermc.paper.TickingLevel.ENTITY); ++ public TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { ++ return addKeyedPluginChunkTicket(x, z, key, TickingLevel.ENTITY); + } + + @Override -+ public io.papermc.paper.TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key, io.papermc.paper.TickingLevel level) { ++ public TickingLevel addKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key, TickingLevel level) { + Preconditions.checkArgument(key != null, "null key"); + Preconditions.checkArgument(level != null, "null level"); + -+ io.papermc.paper.TickingLevel previousLevel = getKeyedPluginChunkTicketLevel(x, z, key); ++ TickingLevel previousLevel = getKeyedPluginChunkTicketLevel(x, z, key); + if (previousLevel == level) { + return level; //ticket already present at desired level -> do nothing + } @@ -145,13 +145,13 @@ index b71316cce3bdbf3485be456f0260c6b3463cff8e..ca4d6883385c5a2f357c7caba7291b77 + } + + @Override -+ public io.papermc.paper.TickingLevel removeKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { ++ public TickingLevel removeKeyedPluginChunkTicket(int x, int z, org.bukkit.NamespacedKey key) { + Preconditions.checkArgument(key != null, "null key"); + + ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; + ChunkCoordIntPair coordPair = new ChunkCoordIntPair(x, z); + -+ for (io.papermc.paper.TickingLevel level : io.papermc.paper.TickingLevel.values()) { ++ for (TickingLevel level : TickingLevel.values()) { + int ticketLevel = MCUtil.getTicketLevelFrom(level); + if (manager.removeTicketAtLevel(TicketType.KEYED_PLUGIN_TICKET, coordPair, ticketLevel, key)) { + return level; @@ -161,7 +161,7 @@ index b71316cce3bdbf3485be456f0260c6b3463cff8e..ca4d6883385c5a2f357c7caba7291b77 + } + + @Override -+ public io.papermc.paper.TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, org.bukkit.NamespacedKey key) { ++ public TickingLevel getKeyedPluginChunkTicketLevel(int x, int z, org.bukkit.NamespacedKey key) { + Preconditions.checkArgument(key != null, "null key"); + + ChunkMapDistance manager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager;