From 07b5b06d2c5e58c0b3cf8036dc5623c60726471f Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 8 Jul 2019 12:14:16 +1000 Subject: [PATCH] Add Plugin Chunk Ticket API Allows plugins to force certain chunks to be kept loaded for as long as they are enabled. --- nms-patches/ChunkMapDistance.patch | 137 ++++++++++++++++++ nms-patches/PlayerChunkMap.patch | 9 ++ nms-patches/Ticket.patch | 20 +++ nms-patches/TicketType.patch | 5 +- .../org/bukkit/craftbukkit/CraftChunk.java | 17 +++ .../org/bukkit/craftbukkit/CraftWorld.java | 82 +++++++++++ 6 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 nms-patches/ChunkMapDistance.patch create mode 100644 nms-patches/Ticket.patch diff --git a/nms-patches/ChunkMapDistance.patch b/nms-patches/ChunkMapDistance.patch new file mode 100644 index 0000000000..ceb9d9c25d --- /dev/null +++ b/nms-patches/ChunkMapDistance.patch @@ -0,0 +1,137 @@ +--- a/net/minecraft/server/ChunkMapDistance.java ++++ b/net/minecraft/server/ChunkMapDistance.java +@@ -32,7 +32,7 @@ + private static final Logger LOGGER = LogManager.getLogger(); + private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); +- private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); ++ public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); // CraftBukkit - private -> public + private final ChunkMapDistance.a e = new ChunkMapDistance.a(); + private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); + private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); +@@ -61,7 +61,7 @@ + while (objectiterator.hasNext()) { + Entry>> entry = (Entry) objectiterator.next(); + +- if (((ObjectSortedSet) entry.getValue()).removeIf((ticket) -> { ++ if ((entry.getValue()).removeIf((ticket) -> { // Craftbukkit - decompile error + return ticket.a(this.currentTick); + })) { + this.e.b(entry.getLongKey(), this.a((ObjectSortedSet) entry.getValue()), false); +@@ -124,7 +124,7 @@ + + completablefuture.thenAccept((either) -> { + this.m.execute(() -> { +- this.k.a((Object) ChunkTaskQueueSorter.a(() -> { ++ this.k.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + }, j, false)); + }); + }); +@@ -138,7 +138,7 @@ + } + } + +- private void a(long i, Ticket ticket) { ++ private boolean a(long i, Ticket ticket) { // CraftBukkit - void -> boolean // PAIL addTicket + ObjectSortedSet> objectsortedset = this.e(i); + ObjectBidirectionalIterator> objectbidirectionaliterator = objectsortedset.iterator(); + int j; +@@ -149,21 +149,24 @@ + j = PlayerChunkMap.GOLDEN_TICKET + 1; + } + ++ boolean ret = false; // CraftBukkit + if (objectsortedset.add(ticket)) { +- ; ++ ret = true; // CraftBukkit + } + + if (ticket.b() < j) { + this.e.b(i, ticket.b(), true); + } + ++ return ret; // CraftBukkit + } + +- private void b(long i, Ticket ticket) { ++ private boolean b(long i, Ticket ticket) { // CraftBukkit - void -> boolean // PAIL removeTicket + ObjectSortedSet> objectsortedset = this.e(i); + ++ boolean removed = false; // CraftBukkit + if (objectsortedset.remove(ticket)) { +- ; ++ removed = true; // CraftBukkit + } + + if (objectsortedset.isEmpty()) { +@@ -171,16 +174,27 @@ + } + + this.e.b(i, this.a(objectsortedset), false); ++ return removed; // CraftBukkit + } + + public void a(TicketType tickettype, ChunkCoordIntPair chunkcoordintpair, int i, T t0) { +- this.a(chunkcoordintpair.pair(), new Ticket<>(tickettype, i, t0, this.currentTick)); ++ // CraftBukkit start ++ this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0); ++ } ++ public boolean addTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int level, T identifier) { ++ return this.a(chunkPos.pair(), new Ticket<>(ticketType, level, identifier, this.currentTick)); ++ // CraftBukkit end + } + + public void b(TicketType tickettype, ChunkCoordIntPair chunkcoordintpair, int i, T t0) { +- Ticket ticket = new Ticket<>(tickettype, i, t0, this.currentTick); ++ // CraftBukkit start ++ this.removeTicketAtLevel(tickettype, chunkcoordintpair, i, t0); ++ } ++ public boolean removeTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int level, T identifier) { ++ Ticket ticket = new Ticket<>(ticketType, level, identifier, this.currentTick); + +- this.b(chunkcoordintpair.pair(), ticket); ++ return this.b(chunkPos.pair(), ticket); ++ // CraftBukkit end + } + + public void addTicket(TicketType tickettype, ChunkCoordIntPair chunkcoordintpair, int i, T t0) { +@@ -247,6 +261,21 @@ + return this.f.a.containsKey(i); + } + ++ // CraftBukkit start ++ public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { ++ Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier, this.currentTick); ++ ++ for (java.util.Iterator>> iterator = this.tickets.values().iterator(); iterator.hasNext();) { ++ ObjectSortedSet> tickets = iterator.next(); ++ tickets.remove(target); ++ ++ if (tickets.isEmpty()) { ++ iterator.remove(); ++ } ++ } ++ } ++ // CraftBukkit end ++ + class a extends ChunkMap { + + public a() { +@@ -333,7 +362,7 @@ + Ticket ticket = new Ticket<>(TicketType.PLAYER, ChunkMapDistance.b, new ChunkCoordIntPair(i), ChunkMapDistance.this.currentTick); + + if (flag1) { +- ChunkMapDistance.this.j.a((Object) ChunkTaskQueueSorter.a(() -> { ++ ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { + ChunkMapDistance.this.a(i, ticket); + ChunkMapDistance.this.l.add(i); +@@ -342,7 +371,7 @@ + return j; + })); + } else { +- ChunkMapDistance.this.k.a((Object) ChunkTaskQueueSorter.a(() -> { ++ ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // Craftbukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { + ChunkMapDistance.this.b(i, ticket); + }); diff --git a/nms-patches/PlayerChunkMap.patch b/nms-patches/PlayerChunkMap.patch index 1b78eafa5f..4890150367 100644 --- a/nms-patches/PlayerChunkMap.patch +++ b/nms-patches/PlayerChunkMap.patch @@ -8,6 +8,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -58,7 +59,7 @@ + private final Mailbox> mailboxWorldGen; + private final Mailbox> mailboxMain; + public final WorldLoadListener worldLoadListener; +- private final PlayerChunkMap.a u; ++ public final PlayerChunkMap.a u; // CraftBukkit - private -> public // PAIL chunkDistanceManager + private final AtomicInteger v; + private final DefinedStructureManager definedStructureManager; + private final File x; @@ -184,9 +185,12 @@ return completablefuture1.thenApply((list1) -> { diff --git a/nms-patches/Ticket.patch b/nms-patches/Ticket.patch new file mode 100644 index 0000000000..aa2c539482 --- /dev/null +++ b/nms-patches/Ticket.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/server/Ticket.java ++++ b/net/minecraft/server/Ticket.java +@@ -6,7 +6,7 @@ + + private final TicketType a; + private final int b; +- private final T c; ++ public final T c; // CraftBukkit - private -> public // PAIL identifier + private final long d; + + protected Ticket(TicketType tickettype, int i, T t0, long j) { +@@ -24,7 +24,7 @@ + } else { + int j = Integer.compare(System.identityHashCode(this.a), System.identityHashCode(ticket.a)); + +- return j != 0 ? j : this.a.a().compare(this.c, ticket.c); ++ return j != 0 ? j : this.a.a().compare(this.c, (T) ticket.c); // CraftBukkit - decompile error + } + } + diff --git a/nms-patches/TicketType.patch b/nms-patches/TicketType.patch index dd2a2a4b35..0ccbf0697f 100644 --- a/nms-patches/TicketType.patch +++ b/nms-patches/TicketType.patch @@ -9,15 +9,16 @@ public static final TicketType START = a("start", (unit, unit1) -> { return 0; }); -@@ -19,6 +19,7 @@ +@@ -19,6 +19,8 @@ public static final TicketType PORTAL = a("portal", Comparator.comparingLong(BlockPosition2D::b)); public static final TicketType POST_TELEPORT = a("post_teleport", Integer::compareTo, 5); 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 TicketType a(String s, Comparator comparator) { return new TicketType<>(s, comparator, 0L); -@@ -45,4 +46,10 @@ +@@ -45,4 +47,10 @@ public long b() { return this.k; } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index f4dfbf534d..9f70019d2f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -3,6 +3,7 @@ package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; import java.lang.ref.WeakReference; import java.util.Arrays; +import java.util.Collection; import net.minecraft.server.BiomeBase; import net.minecraft.server.BlockPosition; import net.minecraft.server.Blocks; @@ -27,6 +28,7 @@ import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; public class CraftChunk implements Chunk { private WeakReference weakChunk; @@ -182,6 +184,21 @@ public class CraftChunk implements Chunk { getWorld().setChunkForceLoaded(getX(), getZ(), forced); } + @Override + public boolean addPluginChunkTicket(Plugin plugin) { + return getWorld().addPluginChunkTicket(getX(), getZ(), plugin); + } + + @Override + public boolean removePluginChunkTicket(Plugin plugin) { + return getWorld().removePluginChunkTicket(getX(), getZ(), plugin); + } + + @Override + public Collection getPluginChunkTickets() { + return getWorld().getPluginChunkTickets(getX(), getZ()); + } + @Override public ChunkSnapshot getChunkSnapshot() { return getChunkSnapshot(true, false, false); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index c3c0d0a0f6..161bed2982 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1,6 +1,8 @@ package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import java.io.File; import java.io.IOException; @@ -17,6 +19,8 @@ import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.function.Predicate; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectSortedSet; import net.minecraft.server.AxisAlignedBB; import net.minecraft.server.BiomeBase; import net.minecraft.server.BlockChorusFlower; @@ -24,6 +28,7 @@ import net.minecraft.server.BlockDiodeAbstract; import net.minecraft.server.BlockPosition; import net.minecraft.server.Blocks; import net.minecraft.server.ChunkCoordIntPair; +import net.minecraft.server.ChunkMapDistance; import net.minecraft.server.ChunkStatus; import net.minecraft.server.EntityAreaEffectCloud; import net.minecraft.server.EntityArmorStand; @@ -76,6 +81,7 @@ import net.minecraft.server.PlayerChunk; import net.minecraft.server.ProtoChunkExtension; import net.minecraft.server.RayTrace; import net.minecraft.server.SoundCategory; +import net.minecraft.server.Ticket; import net.minecraft.server.TicketType; import net.minecraft.server.Unit; import net.minecraft.server.Vec3D; @@ -471,6 +477,82 @@ public class CraftWorld implements World { ((CraftChunk) getChunkAt(chunk.getX(), chunk.getZ())).getHandle().bukkitChunk = chunk; } + @Override + public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { + Preconditions.checkArgument(plugin != null, "null plugin"); + Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); + + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.u; + + if (chunkDistanceManager.addTicketAtLevel(TicketType.PLUGIN_TICKET, new ChunkCoordIntPair(x, z), 31, plugin)) { // keep in-line with force loading, add at level 31 + this.getChunkAt(x, z); // ensure loaded + return true; + } + + return false; + } + + @Override + public boolean removePluginChunkTicket(int x, int z, Plugin plugin) { + Preconditions.checkNotNull(plugin, "null plugin"); + + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.u; + return chunkDistanceManager.removeTicketAtLevel(TicketType.PLUGIN_TICKET, new ChunkCoordIntPair(x, z), 31, plugin); // keep in-line with force loading, remove at level 31 + } + + @Override + public void removePluginChunkTickets(Plugin plugin) { + Preconditions.checkNotNull(plugin, "null plugin"); + + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.u; + chunkDistanceManager.removeAllTicketsFor(TicketType.PLUGIN_TICKET, 31, plugin); // keep in-line with force loading, remove at level 31 + } + + @Override + public Collection getPluginChunkTickets(int x, int z) { + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.u; + ObjectSortedSet> tickets = chunkDistanceManager.tickets.get(ChunkCoordIntPair.pair(x, z)); + + if (tickets == null) { + return Collections.emptyList(); + } + + ImmutableList.Builder ret = ImmutableList.builder(); + for (Ticket ticket : tickets) { + if (ticket.getTicketType() == TicketType.PLUGIN_TICKET) { + ret.add((Plugin) ticket.c); + } + } + + return ret.build(); + } + + @Override + public Map> getPluginChunkTickets() { + Map> ret = new HashMap<>(); + ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.u; + + for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { + long chunkKey = chunkTickets.getLongKey(); + ObjectSortedSet> tickets = chunkTickets.getValue(); + + Chunk chunk = null; + for (Ticket ticket : tickets) { + if (ticket.getTicketType() != TicketType.PLUGIN_TICKET) { + continue; + } + + if (chunk == null) { + chunk = this.getChunkAt(ChunkCoordIntPair.getX(chunkKey), ChunkCoordIntPair.getZ(chunkKey)); + } + + ret.computeIfAbsent((Plugin) ticket.c, (key) -> ImmutableList.builder()).add(chunk); + } + } + + return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); + } + @Override public boolean isChunkForceLoaded(int x, int z) { return getHandle().getForceLoadedChunks().contains(ChunkCoordIntPair.pair(x, z));