mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 23:38:25 +01:00
Foundational work for Future Memory usage improvements
This commit doesn't do much on its own, but adds a new Java Cleaner API that lets us hook into Garbage Collector events to reclaim pooled objects and return them to the pool. Adds framework for Network Packets to know when a packet has finished dispatching to get an idea when a packet is done sending to players. Rewrites PooledObjects impl to properly respect max pool size and remove almost all risk of contention. Bumps the Paper Async Task Queue to use 2 threads, and properly shuts it down on shutdown.
This commit is contained in:
parent
4e7644ce04
commit
90072b811c
7 changed files with 284 additions and 163 deletions
|
@ -1587,11 +1587,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ // apply fixes
|
||||
+
|
||||
+ try {
|
||||
+ if (chunkData.poiData != null) {
|
||||
+ chunkData.poiData = chunkData.poiData.clone(); // clone data for safety, file IO thread does not clone
|
||||
+ }
|
||||
+ chunkData.chunkData = chunkManager.getChunkData(this.world.getWorldProvider().getDimensionManager(),
|
||||
+ chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData.clone(), chunkPos, this.world); // clone data for safety, file IO thread does not clone
|
||||
+ chunkManager.getWorldPersistentDataSupplier(), chunkData.chunkData, chunkPos, this.world); // clone data for safety, file IO thread does not clone
|
||||
+ } catch (final Throwable ex) {
|
||||
+ PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex);
|
||||
+ this.complete(ChunkLoadTask.createEmptyHolder());
|
||||
|
|
|
@ -226,7 +226,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -0,0 +0,0 @@ public final class MCUtil {
|
||||
|
||||
return null;
|
||||
|
|
|
@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ <version>8.0.1</version> <!-- Paper -->
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- deprecated API depend -->
|
||||
<dependency>
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
|
||||
|
|
|
@ -2083,84 +2083,167 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
@@ -0,0 +0,0 @@
|
||||
+package com.destroystokyo.paper.util.pooled;
|
||||
+
|
||||
+import net.minecraft.server.MCUtil;
|
||||
+import org.apache.commons.lang3.mutable.MutableInt;
|
||||
+
|
||||
+import java.util.ArrayDeque;
|
||||
+import java.util.concurrent.ThreadLocalRandom;
|
||||
+import java.util.concurrent.atomic.AtomicLong;
|
||||
+import java.util.concurrent.locks.ReentrantLock;
|
||||
+import java.util.function.Consumer;
|
||||
+
|
||||
+/**
|
||||
+ * Object pooling with thread safe, low contention design. Pooled objects have no additional object overhead
|
||||
+ * due to usage of ArrayDeque per insertion/removal unless a resizing is needed in the buckets.
|
||||
+ * Supports up to bucket size (default 8) threads concurrently accessing if all buckets have a value.
|
||||
+ * Releasing may conditionally have contention if multiple buckets have same current size, but randomization will be used.
|
||||
+ *
|
||||
+ * Original interface API by Spottedleaf
|
||||
+ * Implementation by Aikar <aikar@aikar.co>
|
||||
+ * @license MIT
|
||||
+ */
|
||||
+public final class PooledObjects<E> {
|
||||
+
|
||||
+ public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 200, -1);
|
||||
+ /**
|
||||
+ * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool.
|
||||
+ */
|
||||
+ public class AutoReleased {
|
||||
+ private final E object;
|
||||
+ private final Runnable cleaner;
|
||||
+
|
||||
+ public AutoReleased(E object, Runnable cleaner) {
|
||||
+ this.object = object;
|
||||
+ this.cleaner = cleaner;
|
||||
+ }
|
||||
+
|
||||
+ public final E getObject() {
|
||||
+ return object;
|
||||
+ }
|
||||
+
|
||||
+ public final Runnable getCleaner() {
|
||||
+ return cleaner;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024, 16);
|
||||
+
|
||||
+ private final PooledObjectHandler<E> handler;
|
||||
+ private final int maxPoolSize;
|
||||
+ private final int expectingThreads;
|
||||
+ private final int bucketCount;
|
||||
+ private final int bucketSize;
|
||||
+ private final ArrayDeque<E>[] buckets;
|
||||
+ private final ReentrantLock[] locks;
|
||||
+ private final AtomicLong bucketIdCounter = new AtomicLong(0);
|
||||
+
|
||||
+ private final IsolatedPool<E> mainPool;
|
||||
+ // use these under contention
|
||||
+ private final IsolatedPool<E>[] contendedPools;
|
||||
+
|
||||
+ public PooledObjects(final PooledObjectHandler<E> handler, final int maxPoolSize, int expectingThreads) {
|
||||
+ public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize) {
|
||||
+ this(handler, maxPoolSize, 8);
|
||||
+ }
|
||||
+ public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize, int bucketCount) {
|
||||
+ if (handler == null) {
|
||||
+ throw new NullPointerException("Handler must not be null");
|
||||
+ }
|
||||
+ if (maxPoolSize <= 0) {
|
||||
+ throw new IllegalArgumentException("Max pool size must be greater-than 0");
|
||||
+ }
|
||||
+ if (expectingThreads <= 0) {
|
||||
+ expectingThreads = Runtime.getRuntime().availableProcessors();
|
||||
+ if (bucketCount < 1) {
|
||||
+ throw new IllegalArgumentException("Bucket count must be greater-than 0");
|
||||
+ }
|
||||
+
|
||||
+ int remainder = maxPoolSize % bucketCount;
|
||||
+ if (remainder > 0) {
|
||||
+ // Auto adjust up to the next bucket divisible size
|
||||
+ maxPoolSize = maxPoolSize - remainder + bucketCount;
|
||||
+ }
|
||||
+ //noinspection unchecked
|
||||
+ this.buckets = new ArrayDeque[bucketCount];
|
||||
+ this.locks = new ReentrantLock[bucketCount];
|
||||
+ this.bucketCount = bucketCount;
|
||||
+ this.handler = handler;
|
||||
+ this.maxPoolSize = maxPoolSize;
|
||||
+ this.expectingThreads = expectingThreads;
|
||||
+ this.mainPool = new IsolatedPool<>(handler, maxPoolSize);
|
||||
+ final IsolatedPool<E>[] contendedPools = new IsolatedPool[2 * expectingThreads];
|
||||
+ this.bucketSize = maxPoolSize / bucketCount;
|
||||
+ for (int i = 0; i < bucketCount; i++) {
|
||||
+ this.buckets[i] = new ArrayDeque<>(bucketSize / 4);
|
||||
+ this.locks[i] = new ReentrantLock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < contendedPools.length; ++i) {
|
||||
+ contendedPools[i] = new IsolatedPool<>(handler, Math.max(1, maxPoolSize / 2));
|
||||
+ public AutoReleased acquireCleaner(Object holder) {
|
||||
+ return acquireCleaner(holder, this::release);
|
||||
+ }
|
||||
+
|
||||
+ public AutoReleased acquireCleaner(Object holder, Consumer<E> releaser) {
|
||||
+ E resource = acquire();
|
||||
+ Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser);
|
||||
+ return new AutoReleased(resource, cleaner);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ public long size() {
|
||||
+ long size = 0;
|
||||
+ for (int i = 0; i < bucketCount; i++) {
|
||||
+ size += this.buckets[i].size();
|
||||
+ }
|
||||
+
|
||||
+ this.contendedPools = contendedPools;
|
||||
+ return size;
|
||||
+ }
|
||||
+
|
||||
+ // Taken from
|
||||
+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
|
||||
+ // Original license is public domain
|
||||
+ public static int fastRandomBounded(final long randomInteger, final long limit) {
|
||||
+ // randomInteger must be [0, pow(2, 32))
|
||||
+ // limit must be [0, pow(2, 32))
|
||||
+ return (int)((randomInteger * limit) >>> 32);
|
||||
+ }
|
||||
+
|
||||
+ public E acquire() {
|
||||
+ E ret;
|
||||
+ PooledObjects.IsolatedPool<E> pooled = this.mainPool;
|
||||
+ int lastIndex = -1;
|
||||
+ while ((ret = pooled.tryAcquireUncontended()) == null) {
|
||||
+ int index;
|
||||
+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length)));
|
||||
+ lastIndex = index;
|
||||
+ pooled = this.contendedPools[index];
|
||||
+ for (int base = (int) (this.bucketIdCounter.getAndIncrement() % bucketCount), i = 0; i < bucketCount; i++ ) {
|
||||
+ int bucketId = (base + i) % bucketCount;
|
||||
+ if (this.buckets[bucketId].isEmpty()) continue;
|
||||
+ // lock will alloc an object if blocked, so spinwait instead since lock duration is super fast
|
||||
+ lockBucket(bucketId);
|
||||
+ E value = this.buckets[bucketId].poll();
|
||||
+ this.locks[bucketId].unlock();
|
||||
+ if (value != null) {
|
||||
+ this.handler.onAcquire(value);
|
||||
+ return value;
|
||||
+ }
|
||||
+ }
|
||||
+ return this.handler.createNew();
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+ private void lockBucket(int bucketId) {
|
||||
+ // lock will alloc an object if blocked, try to avoid unless 2 failures
|
||||
+ ReentrantLock lock = this.locks[bucketId];
|
||||
+ if (!lock.tryLock()) {
|
||||
+ Thread.yield();
|
||||
+ } else {
|
||||
+ return;
|
||||
+ }
|
||||
+ if (!lock.tryLock()) {
|
||||
+ Thread.yield();
|
||||
+ lock.lock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void release(final E value) {
|
||||
+ PooledObjects.IsolatedPool<E> pooled = this.mainPool;
|
||||
+ int lastIndex = -1;
|
||||
+ while (!pooled.tryReleaseUncontended(value)) {
|
||||
+ int index;
|
||||
+ while (lastIndex == (index = fastRandomBounded(ThreadLocalRandom.current().nextInt() & 0xFFFFFFFFL, this.contendedPools.length)));
|
||||
+ lastIndex = index;
|
||||
+ pooled = this.contendedPools[index];
|
||||
+ }
|
||||
+ int attempts = 3; // cap on contention
|
||||
+ do {
|
||||
+ // find least filled bucket before locking
|
||||
+ int smallestIdx = -1;
|
||||
+ int smallest = Integer.MAX_VALUE;
|
||||
+ for (int i = 0; i < bucketCount; i++ ) {
|
||||
+ ArrayDeque<E> bucket = this.buckets[i];
|
||||
+ int size = bucket.size();
|
||||
+ if (size < this.bucketSize && (smallestIdx == -1 || size < smallest || (size == smallest && ThreadLocalRandom.current().nextBoolean()))) {
|
||||
+ smallestIdx = i;
|
||||
+ smallest = size;
|
||||
+ }
|
||||
+ }
|
||||
+ if (smallestIdx == -1) return; // Can not find a bucket to fill
|
||||
+
|
||||
+ lockBucket(smallestIdx);
|
||||
+ ArrayDeque<E> bucket = this.buckets[smallestIdx];
|
||||
+ if (bucket.size() < this.bucketSize) {
|
||||
+ this.handler.onRelease(value);
|
||||
+ bucket.push(value);
|
||||
+ this.locks[smallestIdx].unlock();
|
||||
+ return;
|
||||
+ } else {
|
||||
+ this.locks[smallestIdx].unlock();
|
||||
+ }
|
||||
+ } while (attempts-- > 0);
|
||||
+ }
|
||||
+
|
||||
+ /** This object is restricted from interacting with any pool */
|
||||
+ public static interface PooledObjectHandler<E> {
|
||||
+ public interface PooledObjectHandler<E> {
|
||||
+
|
||||
+ /**
|
||||
+ * Must return a non-null object
|
||||
|
@ -2171,89 +2254,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+
|
||||
+ default void onRelease(final E value) {}
|
||||
+ }
|
||||
+
|
||||
+ protected static class IsolatedPool<E> {
|
||||
+
|
||||
+ protected final PooledObjectHandler<E> handler;
|
||||
+
|
||||
+ // We use arraydeque as it doesn't create garbage per element...
|
||||
+ protected final ArrayDeque<E> pool;
|
||||
+ protected final int maxPoolSize;
|
||||
+
|
||||
+ protected final ReentrantLock lock = new ReentrantLock();
|
||||
+
|
||||
+ public IsolatedPool(final PooledObjectHandler<E> handler, final int maxPoolSize) {
|
||||
+ this.handler = handler;
|
||||
+ this.pool = new ArrayDeque<>();
|
||||
+ this.maxPoolSize = maxPoolSize;
|
||||
+ }
|
||||
+
|
||||
+ protected E acquireOrCreateNoLock() {
|
||||
+ E ret;
|
||||
+
|
||||
+ ret = this.pool.poll();
|
||||
+
|
||||
+ if (ret == null) {
|
||||
+ ret = this.handler.createNew();
|
||||
+ }
|
||||
+ this.handler.onAcquire(ret);
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ public E tryAcquireUncontended() {
|
||||
+ if (!this.lock.tryLock()) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ try {
|
||||
+ return this.acquireOrCreateNoLock();
|
||||
+ } finally {
|
||||
+ this.lock.unlock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public E acquire() {
|
||||
+ this.lock.lock();
|
||||
+ try {
|
||||
+ return this.acquireOrCreateNoLock();
|
||||
+ } finally {
|
||||
+ this.lock.unlock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ protected void releaseNoLock(final E value) {
|
||||
+ if (this.pool.size() >= this.maxPoolSize) {
|
||||
+ this.handler.onRelease(value);
|
||||
+ return; // can't accept, we're at capacity
|
||||
+ }
|
||||
+
|
||||
+ this.pool.add(value);
|
||||
+ this.handler.onRelease(value);
|
||||
+ }
|
||||
+
|
||||
+ public boolean tryReleaseUncontended(final E value) {
|
||||
+ if (!this.lock.tryLock()) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ this.releaseNoLock(value);
|
||||
+ } finally {
|
||||
+ this.lock.unlock();
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ public void release(final E value) {
|
||||
+ this.lock.lock();
|
||||
+ try {
|
||||
+ this.releaseNoLock(value);
|
||||
+ } finally {
|
||||
+ this.lock.unlock();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
|
||||
new file mode 100644
|
||||
|
@ -3355,21 +3355,80 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+import java.util.Queue;
|
||||
+import java.util.concurrent.CompletableFuture;
|
||||
+import java.util.concurrent.ExecutionException;
|
||||
+import java.util.concurrent.Executor;
|
||||
+import java.util.concurrent.Executors;
|
||||
+import java.util.concurrent.LinkedBlockingQueue;
|
||||
+import java.util.concurrent.ThreadPoolExecutor;
|
||||
+import java.util.concurrent.TimeUnit;
|
||||
+import java.util.concurrent.TimeoutException;
|
||||
+import java.util.concurrent.atomic.AtomicBoolean;
|
||||
+import java.util.function.Consumer;
|
||||
+import java.util.function.Supplier;
|
||||
+
|
||||
+public final class MCUtil {
|
||||
+ private static final Executor asyncExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build());
|
||||
+ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
|
||||
+ 0, 2, 60L, TimeUnit.SECONDS,
|
||||
+ new LinkedBlockingQueue<Runnable>(),
|
||||
+ new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build()
|
||||
+ );
|
||||
+ public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor(
|
||||
+ 1, 1, 0L, TimeUnit.SECONDS,
|
||||
+ new LinkedBlockingQueue<Runnable>(),
|
||||
+ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build()
|
||||
+ );
|
||||
+
|
||||
+ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
+
|
||||
+ public static void ensureTickThread(final String reason) {
|
||||
+ if (MinecraftServer.getServer().serverThread != Thread.currentThread()) {
|
||||
+ throw new IllegalStateException(reason);
|
||||
+ }
|
||||
+
|
||||
+ public static Runnable once(Runnable run) {
|
||||
+ AtomicBoolean ran = new AtomicBoolean(false);
|
||||
+ return () -> {
|
||||
+ if (ran.compareAndSet(false, true)) {
|
||||
+ run.run();
|
||||
+ }
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ private static Runnable makeCleanerCallback(Runnable run) {
|
||||
+ return once(() -> cleanerExecutor.execute(run));
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
||||
+ * @param obj
|
||||
+ * @param run
|
||||
+ * @return
|
||||
+ */
|
||||
+ public static Runnable registerCleaner(Object obj, Runnable run) {
|
||||
+ // Wrap callback in its own method above or the lambda will leak object
|
||||
+ Runnable cleaner = makeCleanerCallback(run);
|
||||
+ co.aikar.cleaner.Cleaner.register(obj, cleaner);
|
||||
+ return cleaner;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
||||
+ * @param obj
|
||||
+ * @param list
|
||||
+ * @param cleaner
|
||||
+ * @param <T>
|
||||
+ * @return
|
||||
+ */
|
||||
+ public static <T> Runnable registerListCleaner(Object obj, List<T> list, Consumer<T> cleaner) {
|
||||
+ return registerCleaner(obj, () -> {
|
||||
+ list.forEach(cleaner);
|
||||
+ list.clear();
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
||||
+ * @param obj
|
||||
+ * @param resource
|
||||
+ * @param cleaner
|
||||
+ * @param <T>
|
||||
+ * @return
|
||||
+ */
|
||||
+ public static <T> Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer<T> cleaner) {
|
||||
+ return registerCleaner(obj, () -> cleaner.accept(resource));
|
||||
+ }
|
||||
+
|
||||
+ public static List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
|
||||
|
@ -3753,6 +3812,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
||||
}
|
||||
|
||||
// Spigot start
|
||||
+ MCUtil.asyncExecutor.shutdown(); // Paper
|
||||
+ try { MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
|
||||
+ } catch (java.lang.InterruptedException ignored) {} // Paper
|
||||
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
||||
LOGGER.info("Saving usercache.json");
|
||||
this.getUserCache().c();
|
||||
diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
|
||||
|
|
|
@ -5,7 +5,6 @@ Subject: [PATCH] Optimize Network Manager and add advanced packet support
|
|||
|
||||
Adds ability for 1 packet to bundle other packets to follow it
|
||||
Adds ability for a packet to delay sending more packets until a state is ready.
|
||||
Adds ability to clean up a packet when it is finished (not sent, or finished encoding), such as freeing buffers
|
||||
|
||||
Removes synchronization from sending packets
|
||||
Removes processing packet queue off of main thread
|
||||
|
@ -51,6 +50,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
this.packetListener = packetlistener;
|
||||
}
|
||||
+ // Paper start
|
||||
+ private EntityPlayer getPlayer() {
|
||||
+ if (packetListener instanceof PlayerConnection) {
|
||||
+ return ((PlayerConnection) packetListener).player;
|
||||
+ } else {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up.
|
||||
+ private static java.util.List<Packet> buildExtraPackets(Packet packet) {
|
||||
+ java.util.List<Packet> extra = packet.getExtraPackets();
|
||||
|
@ -95,9 +101,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ // Paper start - handle oversized packets better
|
||||
+ boolean connected = this.isConnected();
|
||||
+ if (!connected && !preparing) {
|
||||
+ packet.onPacketDone();
|
||||
+ return; // Do nothing
|
||||
+ }
|
||||
+ packet.onPacketDispatch(getPlayer());
|
||||
+ if (connected && (InnerUtil.canSendImmediate(this, packet) || (
|
||||
+ MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() &&
|
||||
+ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
|
||||
|
@ -113,13 +119,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ } else {
|
||||
+ java.util.List<NetworkManager.QueuedPacket> packets = new java.util.ArrayList<>(1 + extraPackets.size());
|
||||
+ packets.add(new NetworkManager.QueuedPacket(packet, null)); // delay the future listener until the end of the extra packets
|
||||
+
|
||||
|
||||
+ for (int i = 0, len = extraPackets.size(); i < len;) {
|
||||
+ Packet extra = extraPackets.get(i);
|
||||
+ boolean end = ++i == len;
|
||||
+ packets.add(new NetworkManager.QueuedPacket(extra, end ? genericfuturelistener : null)); // append listener to the end
|
||||
+ }
|
||||
|
||||
+
|
||||
+ this.packetQueue.addAll(packets); // atomic
|
||||
+ }
|
||||
+ this.sendPacketQueue();
|
||||
|
@ -128,6 +134,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
|
||||
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
|
||||
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
if (genericfuturelistener != null) {
|
||||
channelfuture.addListener(genericfuturelistener);
|
||||
}
|
||||
+ // Paper start
|
||||
+ if (packet.hasFinishListener()) {
|
||||
+ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(getPlayer(), channelFuture));
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
} else {
|
||||
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
if (genericfuturelistener != null) {
|
||||
channelfuture1.addListener(genericfuturelistener);
|
||||
}
|
||||
+ // Paper start
|
||||
+ if (packet.hasFinishListener()) {
|
||||
+ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(getPlayer(), channelFuture));
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
});
|
||||
@@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
}
|
||||
|
||||
|
@ -188,7 +218,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
return this.socketAddress;
|
||||
}
|
||||
|
||||
+ public void clearPacketQueue() { QueuedPacket packet; while ((packet = packetQueue.poll()) != null) packet.getPacket().onPacketDone(); } // Paper
|
||||
+ // Paper start
|
||||
+ public void clearPacketQueue() {
|
||||
+ EntityPlayer player = getPlayer();
|
||||
+ packetQueue.forEach(queuedPacket -> {
|
||||
+ Packet<?> packet = queuedPacket.getPacket();
|
||||
+ if (packet.hasFinishListener()) {
|
||||
+ packet.onPacketDispatchFinish(player, null);
|
||||
+ }
|
||||
+ });
|
||||
+ packetQueue.clear();
|
||||
+ } // Paper end
|
||||
public void close(IChatBaseComponent ichatbasecomponent) {
|
||||
// Spigot Start
|
||||
this.preparing = false;
|
||||
|
@ -213,25 +253,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
void a(T t0);
|
||||
|
||||
// Paper start
|
||||
+ default void onPacketDone() {}
|
||||
+
|
||||
+ /**
|
||||
+ * @param player Null if not at PLAY stage yet
|
||||
+ */
|
||||
+ default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {}
|
||||
+
|
||||
+ /**
|
||||
+ * @param player Null if not at PLAY stage yet
|
||||
+ * @param future Can be null if packet was cancelled
|
||||
+ */
|
||||
+ default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable io.netty.channel.ChannelFuture future) {}
|
||||
+ default boolean hasFinishListener() { return false; }
|
||||
+ default boolean isReady() { return true; }
|
||||
+ default java.util.List<Packet> getExtraPackets() { return null; }
|
||||
default boolean packetTooLarge(NetworkManager manager) {
|
||||
return false;
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/PacketEncoder.java
|
||||
+++ b/src/main/java/net/minecraft/server/PacketEncoder.java
|
||||
@@ -0,0 +0,0 @@ public class PacketEncoder extends MessageToByteEncoder<Packet<?>> {
|
||||
} else {
|
||||
throw throwable;
|
||||
}
|
||||
- }
|
||||
+ } finally { try { packet.onPacketDone(); } catch (Exception e) { e.printStackTrace(); } ; } // Paper
|
||||
|
||||
// Paper start
|
||||
int packetLength = bytebuf.readableBytes();
|
||||
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/server/PlayerList.java
|
||||
|
|
|
@ -14,7 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
||||
// Spigot start
|
||||
} catch (java.lang.InterruptedException ignored) {} // Paper
|
||||
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
||||
LOGGER.info("Saving usercache.json");
|
||||
- this.getUserCache().c();
|
||||
|
|
|
@ -56,6 +56,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
<scope>compile</scope>
|
||||
</dependency>
|
||||
@@ -0,0 +0,0 @@
|
||||
<version>7.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
+ <dependency>
|
||||
+ <!-- wrapper to use either java 8 sun cleaner or java9+ official cleaner -->
|
||||
+ <groupId>co.aikar</groupId>
|
||||
+ <artifactId>cleaner</artifactId>
|
||||
+ <version>1.0-SNAPSHOT</version>
|
||||
+ </dependency>
|
||||
<!-- deprecated API depend -->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
@@ -0,0 +0,0 @@
|
||||
|
||||
<!-- This builds a completely 'ready to start' jar with all dependencies inside -->
|
||||
<build>
|
||||
|
|
Loading…
Reference in a new issue