diff --git a/README.md b/README.md index d958f874..de3176ec 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,6 @@ See the patches list below. [Nacho-0049] Option to disable Enchantment table ticking <--> by Rastrian -[Nacho-????] Async knockback and hit detection packets [Nacho-????] Multithreaded entity tracking [Nacho-????] Ticking fixes, tile optimization, and optional fast math [Nacho-????] Many more config options @@ -172,15 +171,11 @@ See the patches list below. [IonSpigot-0037] Fast Cannon Entity Tracker [InsanePaper-269] Cache Chunk Coordinations -[InsanePaper-390] Heavily optimize Tuinity controlled flush patch [Akarin-0001] Avoid double I/O operation on load player file by tsao chi [Akarin-0010] Save Json list asynchronously [Tuinity-????] Skip updating entity tracker without players -[Tuinity-0017] Allow controlled flushing for network manager by Spottedleaf -[Tuinity-0018] Consolidate flush calls for entity tracker packets -[Tuinity-0052] Optimise non-flush packet sending [SportPaper-0027] Fix head rotation packet spam [SportPaper-0043] Get blocks in Chunk API @@ -231,6 +226,8 @@ See the patches list below. [FalchusSpigot-????] Fix view distance lookup [FalchusSpigot-????] Only send Dragon/Wither Death sounds to same world [FalchusSpigot-????] Improve NetworkManager +[FalchusSpigot-????] Add FastNetworkManager +[FalchusSpigot-????] Async knockback [DashSpigot-0033] Fix SPIGOT-1746: Tile entities may not always tick [DashSpigot-0011] Fix MC-94186: Dragon egg falling in lazy chunks diff --git a/WindSpigot-Server/src/main/java/com/falchus/spigot/optimizations/FastNetworkManager.java b/WindSpigot-Server/src/main/java/com/falchus/spigot/optimizations/FastNetworkManager.java new file mode 100644 index 00000000..2d32a326 --- /dev/null +++ b/WindSpigot-Server/src/main/java/com/falchus/spigot/optimizations/FastNetworkManager.java @@ -0,0 +1,72 @@ +package com.falchus.spigot.optimizations; + +import com.windpvp.windspigot.config.WindSpigotConfig; +import com.google.common.collect.Queues; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import net.minecraft.server.NetworkManager; +import net.minecraft.server.Packet; + +import java.util.ArrayList; +import java.util.Queue; + +public class FastNetworkManager { + + private final NetworkManager networkManager; + private final Queue>[] queues = new Queue[WindSpigotConfig.threadSize]; + + public FastNetworkManager(NetworkManager networkManager) { + this.networkManager = networkManager; + for (int i = 0; i < queues.length; i++) { + queues[i] = Queues.newConcurrentLinkedQueue(); + } + } + + public void writePacketLazily(Packet packet, boolean flush) { + Channel channel = networkManager.channel; + if (channel == null || !channel.isActive()) return; + + channel.eventLoop().execute(() -> { + ChannelFuture future = channel.write(packet); + future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + if (flush) { + channel.flush(); + } + }); + } + + public void writePacketLazily(Packet packet) { + writePacketLazily(packet, false); + } + + public void queuePacket(Packet packet, int trackerThread) { + if (packet != null) { + queues[trackerThread].add(packet); + } + } + + public void flushQueuedPackets() { + Channel channel = networkManager.channel; + if (channel == null || !channel.isActive()) return; + + ArrayList> writing = new ArrayList<>(); + Packet packet; + for (int i = 0; i < queues.length; i++) { + Queue> current = queues[i]; + queues[i] = Queues.newConcurrentLinkedQueue(); + while ((packet = current.poll()) != null) { + writing.add(packet); + } + } + if (writing.isEmpty()) return; + + channel.eventLoop().execute(() -> { + for (Packet p : writing) { + ChannelFuture future = channel.write(p); + future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + channel.flush(); + }); + } +} diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/WindSpigot.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/WindSpigot.java index 077d1ba3..a0854f6b 100644 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/WindSpigot.java +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/WindSpigot.java @@ -14,7 +14,6 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.windpvp.windspigot.async.AsyncUtil; import com.windpvp.windspigot.async.pathsearch.SearchHandler; -import com.windpvp.windspigot.async.thread.CombatThread; import com.windpvp.windspigot.commands.KnockbackCommand; import com.windpvp.windspigot.commands.MobAICommand; import com.windpvp.windspigot.commands.PingCommand; @@ -37,8 +36,6 @@ public class WindSpigot { private static final Logger DEBUG_LOGGER = LogManager.getLogger(); private static WindSpigot INSTANCE; - private CombatThread knockbackThread; - private final Executor statisticsExecutor = Executors .newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("WindSpigot Statistics Thread") .build()); @@ -57,9 +54,6 @@ private WindSpigot() { new SearchHandler(); } - if (WindSpigotConfig.asyncKnockback) { - knockbackThread = new CombatThread("Knockback Thread"); - } if (WindSpigotConfig.asyncTnt) { AsyncExplosions.initExecutor(WindSpigotConfig.fixedPoolSize); } @@ -141,10 +135,6 @@ public static void init() { public StatisticsClient getClient() { return this.client; } - - public CombatThread getKnockbackThread() { - return knockbackThread; - } public static void debug(String msg) { if (WindSpigotConfig.debugMode) diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/entitytracker/AsyncEntityTracker.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/entitytracker/AsyncEntityTracker.java index 7ae5a292..a156368d 100644 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/entitytracker/AsyncEntityTracker.java +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/entitytracker/AsyncEntityTracker.java @@ -29,7 +29,7 @@ public void updatePlayers() { AsyncUtil.run(() -> { for (int index = finalOffset; index < c.size(); index += WindSpigotConfig.trackingThreads) { - ((IndexedLinkedHashSet) c).get(index).update(); + ((IndexedLinkedHashSet) c).get(index).update(finalOffset); } worldServer.ticker.getLatch().decrement(); diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/AsyncPacketThread.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/AsyncPacketThread.java deleted file mode 100644 index 4fa10207..00000000 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/AsyncPacketThread.java +++ /dev/null @@ -1,113 +0,0 @@ -// From -// https://github.com/Argarian-Network/NachoSpigot/tree/async-kb-hit -package com.windpvp.windspigot.async.thread; - -import io.netty.channel.Channel; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -import com.windpvp.windspigot.async.netty.Spigot404Write; -import com.windpvp.windspigot.config.WindSpigotConfig; - -import net.minecraft.server.NetworkManager; -import net.minecraft.server.Packet; - -public abstract class AsyncPacketThread { - private boolean running = true; - private static final long SEC_IN_NANO = 1000000000; - private static final int TPS = WindSpigotConfig.combatThreadTPS; - private static final long TICK_TIME = SEC_IN_NANO / TPS; - private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L; - private Thread thread; - protected Queue packets = new ConcurrentLinkedQueue(); - - public AsyncPacketThread(String s) { - this.thread = new Thread(new Runnable() { - - @Override - public void run() { - AsyncPacketThread.this.loop(); - } - }, s); - this.thread.start(); - } - - - // Loops scanning for new packets to send - public void loop() { - - long lastTick = System.nanoTime(); - long catchupTime = 0L; - - while (this.running) { - long curTime = System.nanoTime(); - long wait = TICK_TIME - (curTime - lastTick); - - if (wait > 0) { - if (catchupTime < 2E6) { - wait += Math.abs(catchupTime); - } else if (wait < catchupTime) { - //catchupTime -= wait; - wait = 0; - } else { - wait -= catchupTime; - //catchupTime = 0; - } - - try { - // Wait a bit before checking for new packets - Thread.sleep(wait / 1000000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - //curTime = System.nanoTime(); - catchupTime = 0L; - continue; - } - - catchupTime = Math.min(MAX_CATCHUP_BUFFER, catchupTime - wait); - - // Handle packets - this.run(); - lastTick = curTime; - } - } - - public abstract void run(); - - // Queue a packet - public void addPacket(final Packet packet, final NetworkManager manager, final GenericFutureListener>[] agenericfuturelistener) { - this.packets.add(new Runnable() { - - @Override - public void run() { - Spigot404Write.writeThenFlush(manager.channel, packet, agenericfuturelistener); - } - }); - } - - public Thread getThread() { - return this.thread; - } - - // Store packet data - public static class RunnableItem { - private Channel channel; - private Packet packet; - - public RunnableItem(Channel m, Packet p) { - this.channel = m; - this.packet = p; - } - - public Packet getPacket() { - return this.packet; - } - - public Channel getChannel() { - return this.channel; - } - } -} \ No newline at end of file diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/CombatThread.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/CombatThread.java deleted file mode 100644 index 9316cb0d..00000000 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/async/thread/CombatThread.java +++ /dev/null @@ -1,17 +0,0 @@ -// From -// https://github.com/Argarian-Network/NachoSpigot/tree/async-kb-hit -package com.windpvp.windspigot.async.thread; - -public class CombatThread extends AsyncPacketThread { - public CombatThread(String s) { - super(s); - } - - // Handle packets - @Override - public void run() { - while (this.packets.size() > 0) { - this.packets.poll().run(); - } - } -} diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java index 0458c2b3..2e42388f 100644 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/config/WindSpigotConfig.java @@ -590,5 +590,16 @@ private static void tabSpam() { tabSpamIncrement = getInt("settings.disconnect-spam.increment", 5); tabSpamLimit = getInt("settings.disconnect-spam.limit", 750); } - + + // FalchusSpigot start + public static int threadSize; + private static void threadSize() { + threadSize = getInt("thread-size", 3); + if (threadSize == -1) { + threadSize = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + } else { + threadSize = Math.max(1, threadSize); + } + } + // FalchusSpigot end } diff --git a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/world/WorldTicker.java b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/world/WorldTicker.java index dc3860dc..694bfe92 100644 --- a/WindSpigot-Server/src/main/java/com/windpvp/windspigot/world/WorldTicker.java +++ b/WindSpigot-Server/src/main/java/com/windpvp/windspigot/world/WorldTicker.java @@ -1,7 +1,5 @@ package com.windpvp.windspigot.world; -import java.util.List; - import com.windpvp.windspigot.async.ResettableLatch; import com.windpvp.windspigot.async.entitytracker.AsyncEntityTracker; import com.windpvp.windspigot.config.WindSpigotConfig; @@ -73,24 +71,7 @@ public void run() { // this.methodProfiler.a("tracker"); if (MinecraftServer.getServer().getPlayerList().getPlayerCount() != 0) // Tuinity { - // Tuinity start - controlled flush for entity tracker packets - List disabledFlushes = new java.util.ArrayList<>( - MinecraftServer.getServer().getPlayerList().getPlayerCount()); - for (EntityPlayer player : MinecraftServer.getServer().getPlayerList().players) { - PlayerConnection connection = player.playerConnection; - if (connection != null) { - connection.networkManager.disableAutomaticFlush(); - disabledFlushes.add(connection.networkManager); - } - } - try { - worldserver.getTracker().updatePlayers(); - } finally { - for (NetworkManager networkManager : disabledFlushes) { - networkManager.enableAutomaticFlush(); - } - } - // Tuinity end - controlled flush for entity tracker packets + worldserver.getTracker().updatePlayers(); } worldserver.timings.tracker.stopTiming(); // Spigot @@ -103,4 +84,4 @@ public ResettableLatch getLatch() { return latch; } -} \ No newline at end of file +} diff --git a/WindSpigot-Server/src/main/java/me/suicidalkids/ion/visuals/CannonTrackerEntry.java b/WindSpigot-Server/src/main/java/me/suicidalkids/ion/visuals/CannonTrackerEntry.java index 4e1fe741..07d5e7dd 100644 --- a/WindSpigot-Server/src/main/java/me/suicidalkids/ion/visuals/CannonTrackerEntry.java +++ b/WindSpigot-Server/src/main/java/me/suicidalkids/ion/visuals/CannonTrackerEntry.java @@ -195,7 +195,7 @@ private void broadcastUpdate() { } @Override - public void updatePlayer(EntityPlayer entityplayer) { + public void updatePlayer(EntityPlayer entityplayer, int trackerThread, boolean immediate) { // Check configurable distance as a cube then visible distance. if (this.c(entityplayer) && this.tracker.h(entityplayer) < 4096.0D) { if (this.tracker instanceof EntityPlayer && withinNoTrack()) { @@ -208,30 +208,29 @@ public void updatePlayer(EntityPlayer entityplayer) { // entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId())); - this.trackedPlayerMap.put(entityplayer, true); // Paper //this.trackedPlayers.add(entityplayer); // WindSpigot - fix cannon tracker //this.trackedPlayers = this.trackedPlayerMap.keySet(); - - Packet packet = this.c(); // IonSpigot - if (packet == null) { - return; // IonSpigot - If it's null don't update the client! - } - entityplayer.playerConnection.queuePacket(packet); + List> queue = immediate ? new ArrayList<>(10) : null; // FalchusSpigot + Packet packet = this.c(); + if (packet == null) return; // FalchusSpigot + + this.trackedPlayerMap.put(entityplayer, true); // PaperBukkit // FalchusSpigot - after null check + + queuePacket(entityplayer, packet, trackerThread, immediate, queue); if (this.tracker.getCustomNameVisible()) { - entityplayer.playerConnection.queuePacket( - new PacketPlayOutEntityMetadata(this.tracker.getId(), this.tracker.getDataWatcher(), true)); + queuePacket(entityplayer, + new PacketPlayOutEntityMetadata(this.tracker.getId(), this.tracker.getDataWatcher(), true), trackerThread, immediate, queue); } - entityplayer.playerConnection.queuePacket(new PacketPlayOutEntityVelocity(this.tracker.getId(), - this.tracker.motX, this.tracker.motY, this.tracker.motZ)); + queuePacket(entityplayer, new PacketPlayOutEntityVelocity(this.tracker.getId(), + this.tracker.motX, this.tracker.motY, this.tracker.motZ), trackerThread, immediate, queue); if (this.tracker.vehicle != null) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle)); + queuePacket(entityplayer, new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle), trackerThread, immediate, queue); } } else if (this.trackedPlayers.contains(entityplayer)) { this.trackedPlayers.remove(entityplayer); @@ -239,4 +238,4 @@ public void updatePlayer(EntityPlayer entityplayer) { } } -} \ No newline at end of file +} diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java index baa25611..5c9f2e04 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java @@ -675,7 +675,7 @@ public void mount(Entity entity) { super.mount(entity); if (this.vehicle != entity1) { // CraftBukkit - this.playerConnection.queuePacket(new PacketPlayOutAttachEntity(0, this, this.vehicle)); // WindSpigot - modify to work with our entity tracker changes (ensure spawn packet sent first) sendPacket -> queuePacket + this.playerConnection.sendPacket(new PacketPlayOutAttachEntity(0, this, this.vehicle)); this.playerConnection.a(this.locX, this.locY, this.locZ, this.yaw, this.pitch); } @@ -1157,7 +1157,7 @@ public ServerStatisticManager getStatisticManager() { } public void d(Entity entity) { - this.playerConnection.queuePacket(new PacketPlayOutEntityDestroy(entity.getId())); // WindSpigot - Queue packet instead of direct send + this.playerConnection.writePacketLazily(new PacketPlayOutEntityDestroy(entity.getId())); } @Override diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java index e377c66a..11e72c81 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -92,6 +92,7 @@ public boolean playerEntitiesUpdated() { private int addRemoveRate; private int addRemoveCooldown; private boolean withinNoTrack = false; + private int trackerThread; // FalchusSpigot // Constructor is used internally (Incompatible with NMS plugins) public EntityTrackerEntry(EntityTracker entityTracker, Entity entity, int b, int c, boolean flag) { @@ -172,10 +173,17 @@ public int hashCode() { } public void update() { + // FalchusSpigot start + update(0); + } + + public void update(int trackerThread) { + this.trackerThread = trackerThread; + // FalchusSpigot end this.withinNoTrack = this.withinNoTrack(); if (--this.addRemoveCooldown <= 0) { this.removeFarPlayers(); - this.addNearPlayers(); + this.addNearPlayers(trackerThread, false); this.addRemoveCooldown = this.addRemoveRate; } @@ -212,20 +220,33 @@ public void processToRemove() { } public void addNearPlayers() { - addNearPlayers(false); + // FalchusSpigot start + addNearPlayers(0, true); + } + + private void addNearPlayers(int trackerThread, boolean immediate) { + addNearPlayers(trackerThread, immediate, false); } - private void addNearPlayers(boolean updateCooldown) { + private void addNearPlayers(int trackerThread, boolean immediate, boolean updateCooldown) { + // FalchusSpigot end if (this.withinNoTrack) { return; } if (updateCooldown) { this.addRemoveCooldown = addRemoveRate; } + // FalchusSpigot start + this.addNearPlayersTrackerThread = trackerThread; + this.addNearPlayersImmediate = immediate; + // FalchusSpigot end this.tracker.world.playerMap.forEachNearby(this.tracker.locX, this.tracker.locY, this.tracker.locZ, this.getRange(), false, addNearPlayersConsumer); } + private int addNearPlayersTrackerThread; + private boolean addNearPlayersImmediate; + private boolean withinNoTrack() { return this.withinNoTrack(this.tracker); } @@ -245,7 +266,7 @@ private boolean withinNoTrack(Entity entity) { @Override public void accept(EntityPlayer entityPlayer) { - updatePlayer(entityPlayer); + updatePlayer(entityPlayer, addNearPlayersTrackerThread, addNearPlayersImmediate); } }; @@ -253,6 +274,12 @@ public void accept(EntityPlayer entityPlayer) { * sends velocity, Location, rotation, and riding info. */ public void track(List list) { + // FalchusSpigot start + Packet vehicleUpdate = null; + Packet movementPacketUpdate = null; + Packet velocityPacketUpdate = null; + Packet headYawUpdate = null; + // FalchusSpigot end this.n = false; if (!this.isMoving || this.tracker.distanceSqured(this.posX, this.posY, this.posZ) > 16.0D) { this.posX = this.tracker.locX; @@ -265,7 +292,7 @@ public void track(List list) { if (this.lastRecoredRider != this.tracker.vehicle || this.tracker.vehicle != null && this.tickCount % 60 == 0) { this.lastRecoredRider = this.tracker.vehicle; - this.broadcastInternal(new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle)); + vehicleUpdate = new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle); } if (this.tracker instanceof EntityItemFrame && this.tickCount % 20 == 0) { // Paper @@ -284,7 +311,7 @@ public void track(List list) { Packet packet = Items.FILLED_MAP.c(itemstack, this.tracker.world, entityplayer); if (packet != null) { - entityplayer.playerConnection.queuePacket(packet); + entityplayer.playerConnection.queuePacket(packet, trackerThread); } } } @@ -367,8 +394,8 @@ public void track(List list) { this.motionX = this.tracker.motX; this.motionY = this.tracker.motY; this.motionZ = this.tracker.motZ; - this.broadcastInternal(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.motionX, this.motionY, - this.motionZ)); + velocityPacketUpdate = new PacketPlayOutEntityVelocity(this.tracker.getId(), this.motionX, this.motionY, + this.motionZ); } } @@ -377,7 +404,7 @@ public void track(List list) { // first update, // since we can't be certain what position they received in the spawn packet. if (object instanceof PacketPlayOutEntityTeleport) { - this.broadcastInternal((Packet) object); + movementPacketUpdate = (Packet) object; } else { PacketPlayOutEntityTeleport teleportPacket = null; @@ -388,9 +415,9 @@ public void track(List list) { teleportPacket = new PacketPlayOutEntityTeleport(this.tracker.getId(), i, j, k, (byte) l, (byte) i1, this.tracker.onGround); } - viewer.getKey().playerConnection.queuePacket(teleportPacket); + viewer.getKey().playerConnection.queuePacket(teleportPacket, trackerThread); } else { - viewer.getKey().playerConnection.queuePacket((Packet) object); + viewer.getKey().playerConnection.queuePacket((Packet) object, trackerThread); } } } @@ -412,8 +439,8 @@ public void track(List list) { boolean flag2 = Math.abs(i - this.yRot) >= 4 || Math.abs(j - this.xRot) >= 4; if (flag2) { - this.broadcastInternal(new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) i, - (byte) j, this.tracker.onGround)); + movementPacketUpdate = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) i, + (byte) j, this.tracker.onGround); this.yRot = i; this.xRot = j; } @@ -427,7 +454,7 @@ public void track(List list) { i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F); if (Math.abs(i - this.lastHeadYaw) >= 4) { - this.broadcastInternal(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i)); + headYawUpdate = new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i); this.lastHeadYaw = i; } @@ -460,6 +487,11 @@ public void track(List list) { this.tracker.velocityChanged = false; } + // FalchusSpigot start + if (vehicleUpdate != null || movementPacketUpdate != null || velocityPacketUpdate != null || headYawUpdate != null) { + queueToWatchers(trackerThread, vehicleUpdate, movementPacketUpdate, velocityPacketUpdate, headYawUpdate); + } + // FalchusSpigot end } private void b() { @@ -499,17 +531,20 @@ public void broadcast(Packet packet) { } - // WindSpigot start - protected void broadcastInternal(Packet packet) { - Iterator iterator = this.trackedPlayers.iterator(); - - while (iterator.hasNext()) { - EntityPlayer entityplayer = (EntityPlayer) iterator.next(); - - entityplayer.playerConnection.queuePacket(packet); + // FalchusSpigot start + private void queueToWatchers(int trackerThread, Packet... packets) { + for (EntityPlayer entityPlayer : trackedPlayers) { + for (Packet packet : packets) { + if (packet != null) { + entityPlayer.playerConnection.queuePacket(packet, trackerThread); + } + } } } - // WindSpigot end + + protected void broadcastInternal(Packet packet) { + queueToWatchers(trackerThread, packet); + } public void broadcastIncludingSelf(Packet packet) { this.broadcast(packet); @@ -519,14 +554,13 @@ public void broadcastIncludingSelf(Packet packet) { } - // WindSpigot start protected void broadcastIncludingSelfInternal(Packet packet) { - this.broadcast(packet); + this.broadcastInternal(packet); if (this.tracker instanceof EntityPlayer) { - ((EntityPlayer) this.tracker).playerConnection.queuePacket(packet); + ((EntityPlayer) this.tracker).playerConnection.queuePacket(packet, trackerThread); } } - // WindSpigot end + // FalchusSpigot end public void a() { Iterator iterator = this.trackedPlayers.iterator(); @@ -547,7 +581,15 @@ public void a(EntityPlayer entityplayer) { } + // FalchusSpigot start public void updatePlayer(EntityPlayer entityplayer) { + if (entityplayer != tracker) { + updatePlayer(entityplayer, 0, true); + } + } + + public void updatePlayer(EntityPlayer entityplayer, int trackerThread, boolean immediate) { + // FalchusSpigot end // org.spigotmc.AsyncCatcher.catchOp( "player tracker update"); // Spigot if (entityplayer != this.tracker) { boolean isPlayerEntityTracked = this.trackedPlayers.contains(entityplayer); @@ -563,20 +605,22 @@ public void updatePlayer(EntityPlayer entityplayer) { // entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId())); // CraftBukkit end - this.trackedPlayerMap.put(entityplayer, true); // PaperBukkit + List> queue = immediate ? new ArrayList<>(10) : null; // FalchusSpigot Packet packet = this.c(); + if (packet == null) return; // FalchusSpigot - entityplayer.playerConnection.queuePacket(packet); + this.trackedPlayerMap.put(entityplayer, true); // PaperBukkit // FalchusSpigot - after null check + + queuePacket(entityplayer, packet, trackerThread, immediate, queue); if (!this.tracker.getDataWatcher().d()) { - entityplayer.playerConnection.queuePacket(new PacketPlayOutEntityMetadata(this.tracker.getId(), - this.tracker.getDataWatcher(), true)); + queuePacket(entityplayer, new PacketPlayOutEntityMetadata(this.tracker.getId(), + this.tracker.getDataWatcher(), true), trackerThread, immediate, queue); } NBTTagCompound nbttagcompound = this.tracker.getNBTTag(); if (nbttagcompound != null) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutUpdateEntityNBT(this.tracker.getId(), nbttagcompound)); + queuePacket(entityplayer, new PacketPlayOutUpdateEntityNBT(this.tracker.getId(), nbttagcompound), trackerThread, immediate, queue); } if (this.tracker instanceof EntityLiving) { @@ -592,8 +636,7 @@ public void updatePlayer(EntityPlayer entityplayer) { // CraftBukkit end if (!collection.isEmpty()) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutUpdateAttributes(this.tracker.getId(), collection)); + queuePacket(entityplayer, new PacketPlayOutUpdateAttributes(this.tracker.getId(), collection), trackerThread, immediate, queue); } } @@ -602,27 +645,26 @@ public void updatePlayer(EntityPlayer entityplayer) { this.motionZ = this.tracker.motZ; if (this.u && !(packet instanceof PacketPlayOutSpawnEntityLiving)) { - entityplayer.playerConnection.queuePacket(new PacketPlayOutEntityVelocity(this.tracker.getId(), - this.tracker.motX, this.tracker.motY, this.tracker.motZ)); + queuePacket(entityplayer, new PacketPlayOutEntityVelocity(this.tracker.getId(), + this.tracker.motX, this.tracker.motY, this.tracker.motZ), trackerThread, immediate, queue); } if (this.tracker.vehicle != null) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle)); + queuePacket(entityplayer, new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle), trackerThread, immediate, queue); } if (this.tracker instanceof EntityInsentient && ((EntityInsentient) this.tracker).getLeashHolder() != null) { - entityplayer.playerConnection.queuePacket(new PacketPlayOutAttachEntity(1, this.tracker, - ((EntityInsentient) this.tracker).getLeashHolder())); + queuePacket(entityplayer, new PacketPlayOutAttachEntity(1, this.tracker, + ((EntityInsentient) this.tracker).getLeashHolder()), trackerThread, immediate, queue); } if (this.tracker instanceof EntityLiving) { for (int i = 0; i < 5; ++i) { ItemStack itemstack = ((EntityLiving) this.tracker).getEquipment(i); if (itemstack != null) { - entityplayer.playerConnection.queuePacket( - new PacketPlayOutEntityEquipment(this.tracker.getId(), i, itemstack)); + queuePacket(entityplayer, + new PacketPlayOutEntityEquipment(this.tracker.getId(), i, itemstack), trackerThread, immediate, queue); } } } @@ -630,8 +672,7 @@ public void updatePlayer(EntityPlayer entityplayer) { if (this.tracker instanceof EntityHuman) { EntityHuman entityhuman = (EntityHuman) this.tracker; if (entityhuman.isSleeping()) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutBed(entityhuman, new BlockPosition(this.tracker))); + queuePacket(entityplayer, new PacketPlayOutBed(entityhuman, new BlockPosition(this.tracker)), trackerThread, immediate, queue); } } @@ -648,8 +689,7 @@ public void updatePlayer(EntityPlayer entityplayer) { // the event loop and flushing the network stream). // this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) // lastHeadYaw)); - entityplayer.playerConnection - .queuePacket(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) lastHeadYaw)); + queuePacket(entityplayer, new PacketPlayOutEntityHeadRotation(this.tracker, (byte) lastHeadYaw), trackerThread, immediate, queue); // SportPaper end } // CraftBukkit end @@ -657,10 +697,15 @@ public void updatePlayer(EntityPlayer entityplayer) { if (this.tracker instanceof EntityLiving) { EntityLiving entityliving = (EntityLiving) this.tracker; for (MobEffect mobeffect : entityliving.getEffects()) { - entityplayer.playerConnection - .queuePacket(new PacketPlayOutEntityEffect(this.tracker.getId(), mobeffect)); + queuePacket(entityplayer, new PacketPlayOutEntityEffect(this.tracker.getId(), mobeffect), trackerThread, immediate, queue); } } + + // FalchusSpigot start + if (immediate && queue != null && !queue.isEmpty()) { + entityplayer.playerConnection.sendPackets(queue, trackerThread); + } + // FalchusSpigot end } } else if (isPlayerEntityTracked) { this.trackedPlayers.remove(entityplayer); @@ -670,6 +715,17 @@ public void updatePlayer(EntityPlayer entityplayer) { } } + // FalchusSpigot start + protected void queuePacket(EntityPlayer entityplayer, Packet packet, int trackerThread, boolean immediate, List> queue) { + if (packet == null) return; + if (immediate) { + queue.add(packet); + } else { + entityplayer.playerConnection.queuePacket(packet, trackerThread); + } + } + // FalchusSpigot end + public boolean c(EntityPlayer entityplayer) { // CraftBukkit start - this.*Loc / 30 -> this.tracker.loc* double d0 = entityplayer.locX - this.tracker.locX; diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java b/WindSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java index 6cf7531f..097522ea 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java @@ -21,6 +21,7 @@ import com.windpvp.windspigot.config.WindSpigotConfig; import com.windpvp.windspigot.exception.ExploitException; +import com.falchus.spigot.optimizations.FastNetworkManager; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -58,6 +59,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build())); // Nacho end + public final FastNetworkManager fastNetworkManager; // FalchusSpigot private final EnumProtocolDirection h; private final Queue i = Queues.newConcurrentLinkedQueue(); public Channel channel; @@ -70,9 +72,6 @@ public class NetworkManager extends SimpleChannelInboundHandler { private PacketListener m; private IChatBaseComponent n; private boolean o; - - // WindSpigot - async kb - private boolean shouldCheckPacket = false; public boolean isEncrypted() { return this.o; @@ -88,37 +87,8 @@ public void setDisconnectionHandled(boolean handled) { this.p = handled; } // Nacho - OBFHELPER - // Tuinity start - allow controlled flushing - volatile boolean canFlush = true; - private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); - private int flushPacketsStart; - private final Object flushLock = new Object(); - - public void disableAutomaticFlush() { - synchronized (this.flushLock) { - this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false - this.canFlush = false; - } - } - - public void enableAutomaticFlush() { - synchronized (this.flushLock) { - this.canFlush = true; - if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true - this.flush(); // only make the flush call if we need to - } - } - } - - private void flush() { - if (this.channel.eventLoop().inEventLoop()) { - this.channel.flush(); - } // [Nacho-Spigot] Fixed RejectedExecutionException: event executor terminated by - // BeyazPolis - } - // Tuinity end - allow controlled flushing - public NetworkManager(EnumProtocolDirection enumprotocoldirection) { + this.fastNetworkManager = new FastNetworkManager(this); // FalchusSpigot this.h = enumprotocoldirection; } @@ -214,24 +184,14 @@ public void a(PacketListener packetlistener) { public void handle(Packet packet) { if (this.isConnected()) { this.sendPacketQueue(); - // WindSpigot start - async kb - // based on https://github.com/Argarian-Network/NachoSpigot/tree/async-kb-hit - if (!shouldCheckPacket) { - // Wait a bit before checking for combat packets to send with priority - // The priority packet writer uses the last context executor - if (this.packetWrites.get() > 5) { - shouldCheckPacket = true; - } - } else { - // Check if the packet is a knockback packet - if (WindSpigotConfig.asyncKnockback && (packet instanceof PacketPlayOutEntityVelocity || packet instanceof PacketPlayOutPosition || packet instanceof PacketPlayInFlying.PacketPlayInPosition || packet instanceof PacketPlayInFlying)) { - // Send it with high priority - WindSpigot.getInstance().getKnockbackThread().addPacket(packet, this, null); - return; - } + // FalchusSpigot start - async kb + // based on https://github.com/Argarian-Network/NachoSpigot/tree/async-kb-hit + if (WindSpigotConfig.asyncKnockback && (packet instanceof PacketPlayOutEntityVelocity || packet instanceof PacketPlayOutPosition)) { + fastNetworkManager.writePacketLazily(packet, true); + return; } - // WindSpigot end - this.dispatchPacket(packet, null, Boolean.TRUE); + // FalchusSpigot end + this.dispatchPacket(packet, null); } else { // FalchusSpigot - remove unnecessary locks for packets (the packet queue is already thread safe) this.i.add(new NetworkManager.QueuedPacket(packet)); @@ -244,7 +204,7 @@ public void a(Packet packet, GenericFutureListener>... listeners) { if (this.isConnected()) { this.sendPacketQueue(); - this.dispatchPacket(packet, ArrayUtils.insert(0, listeners, listener), Boolean.TRUE); + this.dispatchPacket(packet, ArrayUtils.insert(0, listeners, listener)); } else { // FalchusSpigot - remove unnecessary locks for packets (the packet queue is already thread safe) this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.insert(0, listeners, listener))); @@ -263,11 +223,7 @@ public EntityPlayer getPlayer() { // Paper / Nacho end public void dispatchPacket(final Packet packet, - final GenericFutureListener>[] listeners, Boolean flushConditional) { - this.packetWrites.getAndIncrement(); // must be before using canFlush - boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional; - final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive - || packet instanceof PacketPlayOutKickDisconnect; // no delay for certain packets + final GenericFutureListener>[] listeners) { final EnumProtocol enumprotocol = EnumProtocol.getProtocolForPacket(packet); final EnumProtocol enumprotocol1 = this.channel.attr(NetworkManager.ATTRIBUTE_PROTOCOL).get(); if (enumprotocol1 != enumprotocol) { @@ -277,67 +233,36 @@ public void dispatchPacket(final Packet packet, if (enumprotocol != enumprotocol1) { this.setProtocol(enumprotocol); } - ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); + ChannelFuture channelfuture = this.channel.writeAndFlush(packet); if (listeners != null) { channelfuture.addListeners(listeners); } channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } else { - // Tuinity start - optimise packets that are not flushed - Runnable choice1 = null; - AbstractEventExecutor.LazyRunnable choice2 = null; - // note: since the type is not dynamic here, we need to actually copy the old - // executor code - // into two branches. On conflict, just re-copy - no changes were made inside - // the executor code. - if (flush) { - choice1 = () -> { - if (enumprotocol != enumprotocol1) { - this.setProtocol(enumprotocol); - } - try { - ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); // Tuinity - add flush parameter - if (listeners != null) { - channelfuture1.addListeners(listeners); - } - channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } catch (Exception e) { - LOGGER.error("NetworkException: " + getPlayer(), e); - close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage())); - ; - } - }; - } else { - // explicitly declare a variable to make the lambda use the type - choice2 = () -> { - if (enumprotocol != enumprotocol1) { - this.setProtocol(enumprotocol); - } - try { - // Nacho - why not remove the check below if the check is done above? just code - // duplication... - // even IntelliJ screamed at me for doing leaving it like that :shrug: - ChannelFuture channelfuture1 = /* (flush) ? this.channel.writeAndFlush(packet) : */this.channel - .write(packet); // Nacho - see above // Tuinity - add flush parameter - if (listeners != null) { - channelfuture1.addListeners(listeners); - } - channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } catch (Exception e) { - LOGGER.error("NetworkException: " + getPlayer(), e); - close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage())); - ; - } - }; - } - this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2); - // Tuinity end - optimise packets that are not flushed + this.channel.eventLoop().execute(new Runnable() { + @Override + public void run() { + if (enumprotocol != enumprotocol1) { + NetworkManager.this.setProtocol(enumprotocol); + } + try { + ChannelFuture channelfuture = NetworkManager.this.channel.writeAndFlush(packet); + if (listeners != null) { + channelfuture.addListeners(listeners); + } + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } catch (Exception e) { + LOGGER.error("NetworkException: " + getPlayer(), e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage())); + } + } + }); } } private void a(final Packet packet, final GenericFutureListener>[] agenericfuturelistener) { - this.dispatchPacket(packet, agenericfuturelistener, Boolean.TRUE); + this.dispatchPacket(packet, agenericfuturelistener); } private void sendPacketQueue() { @@ -346,19 +271,11 @@ private void sendPacketQueue() { } if (this.channel != null && this.channel.isActive()) { // FalchusSpigot - remove unnecessary locks for packets (the packet queue is already thread safe) - boolean needsFlush = this.canFlush; - boolean hasWrotePacket = false; Iterator iterator = this.i.iterator(); while (iterator.hasNext()) { QueuedPacket queued = iterator.next(); - Packet packet = queued.a; - if (hasWrotePacket && (needsFlush || this.canFlush)) { - flush(); - } iterator.remove(); - this.dispatchPacket(packet, queued.b, - (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); - hasWrotePacket = true; + this.dispatchPacket(queued.a, queued.b); } } } diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java index 46754f9a..50ed6ece 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerConnection.java @@ -1,12 +1,7 @@ package net.minecraft.server; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Queue; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; // CraftBukkit start import java.util.concurrent.ExecutionException; @@ -53,7 +48,6 @@ import org.github.paperspigot.PaperSpigotConfig; // PaperSpigot import com.google.common.collect.Lists; -import com.google.common.collect.Queues; import com.google.common.primitives.Doubles; import com.google.common.primitives.Floats; import com.windpvp.windspigot.WindSpigot; @@ -96,9 +90,6 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList private int creativeSlotCount = 0; private long lastCustomPayloadPacketTS = -1L; private boolean isExploiter = false; - - // WindSpigot - queue-able packets - private Queue> queuedPackets = Queues.newLinkedBlockingQueue(); public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer) { this.minecraftServer = minecraftserver; @@ -2665,20 +2656,32 @@ public boolean isDisconnected() { // Spigot return !this.player.joining && !this.networkManager.channel.config().isAutoRead(); } - // WindSpigot start - queue-able packets - public void queuePacket(Packet packet) { + // FalchusSpigot start - queue-able packets + public void writePacketLazily(Packet packet) { if (packet == null) return; - queuedPackets.add(packet); + networkManager.fastNetworkManager.writePacketLazily(packet); } - - public void sendQueuedPackets() { - networkManager.disableAutomaticFlush(); - while (!queuedPackets.isEmpty()) { - sendPacket(queuedPackets.poll()); + + public void queuePacket(Packet packet, int trackerThread) { + if (packet == null) return; + networkManager.fastNetworkManager.queuePacket(packet, trackerThread); + } + + public void sendPackets(List> packets) { + sendPackets(packets, 0); + } + + public void sendPackets(List> packets, int trackerThread) { + for (Packet packet : packets) { + queuePacket(packet, trackerThread); } - networkManager.enableAutomaticFlush(); + sendQueuedPackets(); + } + + public void sendQueuedPackets() { + networkManager.fastNetworkManager.flushQueuedPackets(); } - // WindSpigot end + // FalchusSpigot end static class SyntheticClass_1 { diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerInteractManager.java b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerInteractManager.java index 4d69b0ef..69b94df5 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -35,7 +35,7 @@ public void setGameMode(WorldSettings.EnumGamemode worldsettings_enumgamemode) { worldsettings_enumgamemode.a(this.player.abilities); this.player.updateAbilities(); this.player.server.getPlayerList() - .sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, + .sendAllLazily(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, new EntityPlayer[] { this.player }), this.player); // CraftBukkit } diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java index ed7fb6a6..e1f4195f 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java @@ -473,7 +473,7 @@ public void onPlayerJoin(EntityPlayer entityplayer, String joinMessage) { // Cra EntityPlayer entityplayer1 = this.players.get(i); if (entityplayer1.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { - entityplayer1.playerConnection.sendPacket(packet); + entityplayer1.playerConnection.writePacketLazily(packet); } if (!entityplayer.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { @@ -1072,7 +1072,7 @@ public void repositionEntity(Entity entity, Location exit, boolean portal) { public void tick() { if (++this.u > 600) { - this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, + this.writeAllLazily(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, this.players)); this.u = 0; } @@ -1086,6 +1086,14 @@ public void sendAll(Packet packet) { } + // FalchusSpigot start + public void writeAllLazily(Packet packet) { + for (int i = 0; i < this.players.size(); ++i) { + this.players.get(i).playerConnection.writePacketLazily(packet); + } + } + // FalchusSpigot end + // CraftBukkit start - add a world/entity limited version public void sendAll(Packet packet, EntityHuman entityhuman) { for (int i = 0; i < this.players.size(); ++i) { @@ -1098,6 +1106,19 @@ public void sendAll(Packet packet, EntityHuman entityhuman) { } } + // FalchusSpigot start + public void sendAllLazily(Packet packet, EntityHuman entityhuman) { + for (int i = 0; i < this.players.size(); ++i) { + EntityPlayer entityplayer = this.players.get(i); + if (entityhuman != null && entityhuman instanceof EntityPlayer + && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { + continue; + } + this.players.get(i).playerConnection.writePacketLazily(packet); + } + } + // FalchusSpigot end + public void sendAll(Packet packet, World world) { for (int i = 0; i < world.players.size(); ++i) { ((EntityPlayer) world.players.get(i)).playerConnection.sendPacket(packet); diff --git a/WindSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/WindSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 2902e1e8..2aba1e85 100644 --- a/WindSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/WindSpigot-Server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1104,7 +1104,7 @@ public void showPlayer(Player player) { EntityTracker tracker = ((WorldServer) entity.world).tracker; EntityPlayer other = ((CraftPlayer) player).getHandle(); - getHandle().playerConnection.sendPacket( + getHandle().playerConnection.writePacketLazily( new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other)); EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());