mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-29 12:37:42 +01:00
Async packet write (#533)
This commit is contained in:
parent
c5e947c76e
commit
8b1856d5b7
@ -131,6 +131,9 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/com.zaxxer/SparseBitSet
|
// https://mvnrepository.com/artifact/com.zaxxer/SparseBitSet
|
||||||
implementation group: 'com.zaxxer', name: 'SparseBitSet', version: '1.2'
|
implementation group: 'com.zaxxer', name: 'SparseBitSet', version: '1.2'
|
||||||
|
|
||||||
|
// https://mvnrepository.com/artifact/org.jctools/jctools-core
|
||||||
|
implementation group: 'org.jctools', name: 'jctools-core', version: '3.3.0'
|
||||||
|
|
||||||
// Guava 21.0+ required for Mixin
|
// Guava 21.0+ required for Mixin
|
||||||
api 'com.google.guava:guava:31.0.1-jre'
|
api 'com.google.guava:guava:31.0.1-jre'
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@ package net.minestom.server;
|
|||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.server.FramedPacket;
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,14 +57,11 @@ public interface Viewable {
|
|||||||
*
|
*
|
||||||
* @param packet the packet to send to all viewers
|
* @param packet the packet to send to all viewers
|
||||||
*/
|
*/
|
||||||
default void sendPacketToViewers(@NotNull ServerPacket packet) {
|
default void sendPacketToViewers(@NotNull SendablePacket packet) {
|
||||||
PacketUtils.sendGroupedPacket(getViewers(), packet);
|
if (packet instanceof ServerPacket serverPacket) {
|
||||||
}
|
PacketUtils.sendGroupedPacket(getViewers(), serverPacket);
|
||||||
|
} else {
|
||||||
@ApiStatus.Experimental
|
getViewers().forEach(player -> player.sendPacket(packet));
|
||||||
default void sendPacketToViewers(@NotNull FramedPacket framedPacket) {
|
|
||||||
for (Player viewer : getViewers()) {
|
|
||||||
viewer.sendPacket(framedPacket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,28 +73,27 @@ public interface Viewable {
|
|||||||
*
|
*
|
||||||
* @param packets the packets to send
|
* @param packets the packets to send
|
||||||
*/
|
*/
|
||||||
default void sendPacketsToViewers(@NotNull ServerPacket... packets) {
|
default void sendPacketsToViewers(@NotNull SendablePacket... packets) {
|
||||||
for (ServerPacket packet : packets) {
|
for (SendablePacket packet : packets) {
|
||||||
PacketUtils.sendGroupedPacket(getViewers(), packet);
|
sendPacketToViewers(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void sendPacketsToViewers(@NotNull Collection<SendablePacket> packets) {
|
||||||
|
packets.forEach(this::sendPacketToViewers);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a packet to all viewers and the viewable element if it is a player.
|
* Sends a packet to all viewers and the viewable element if it is a player.
|
||||||
* <p>
|
* <p>
|
||||||
* If 'this' isn't a player, then only {@link #sendPacketToViewers(ServerPacket)} is called.
|
* If 'this' isn't a player, then only {@link #sendPacketToViewers(SendablePacket)} is called.
|
||||||
*
|
*
|
||||||
* @param packet the packet to send
|
* @param packet the packet to send
|
||||||
*/
|
*/
|
||||||
default void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
default void sendPacketToViewersAndSelf(@NotNull SendablePacket packet) {
|
||||||
sendPacketToViewers(packet);
|
sendPacketToViewers(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
|
||||||
default void sendPacketToViewersAndSelf(@NotNull FramedPacket framedPacket) {
|
|
||||||
sendPacketToViewers(framedPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the result of {@link #getViewers()} as an Adventure Audience.
|
* Gets the result of {@link #getViewers()} as an Adventure Audience.
|
||||||
*
|
*
|
||||||
|
@ -455,7 +455,7 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
|||||||
if (passenger != player) passenger.viewEngine.viewableOption.removal.accept(player);
|
if (passenger != player) passenger.viewEngine.viewableOption.removal.accept(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.sendPacket(destroyPacketCache.retrieve());
|
player.sendPacket(destroyPacketCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,7 +54,7 @@ import net.minestom.server.network.ConnectionState;
|
|||||||
import net.minestom.server.network.PlayerProvider;
|
import net.minestom.server.network.PlayerProvider;
|
||||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||||
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||||
import net.minestom.server.network.packet.server.FramedPacket;
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||||
import net.minestom.server.network.packet.server.play.*;
|
import net.minestom.server.network.packet.server.play.*;
|
||||||
@ -502,17 +502,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
public void sendPacketToViewersAndSelf(@NotNull SendablePacket packet) {
|
||||||
this.playerConnection.sendPacket(packet);
|
this.playerConnection.sendPacket(packet);
|
||||||
super.sendPacketToViewersAndSelf(packet);
|
super.sendPacketToViewersAndSelf(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendPacketToViewersAndSelf(@NotNull FramedPacket framedPacket) {
|
|
||||||
this.playerConnection.sendPacket(framedPacket);
|
|
||||||
super.sendPacketToViewersAndSelf(framedPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the player instance and load surrounding chunks if needed.
|
* Changes the player instance and load surrounding chunks if needed.
|
||||||
* <p>
|
* <p>
|
||||||
@ -1180,18 +1174,23 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for {@link PlayerConnection#sendPacket(ServerPacket)}.
|
* Shortcut for {@link PlayerConnection#sendPacket(SendablePacket)}.
|
||||||
*
|
*
|
||||||
* @param packet the packet to send
|
* @param packet the packet to send
|
||||||
*/
|
*/
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public void sendPacket(@NotNull ServerPacket packet) {
|
public void sendPacket(@NotNull SendablePacket packet) {
|
||||||
this.playerConnection.sendPacket(packet);
|
this.playerConnection.sendPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public void sendPacket(@NotNull FramedPacket framedPacket) {
|
public void sendPackets(@NotNull SendablePacket... packets) {
|
||||||
this.playerConnection.sendPacket(framedPacket);
|
this.playerConnection.sendPackets(packets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public void sendPackets(@NotNull Collection<SendablePacket> packets) {
|
||||||
|
this.playerConnection.sendPackets(packets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,16 +125,14 @@ public class DynamicChunk extends Chunk {
|
|||||||
@Override
|
@Override
|
||||||
public void sendChunk(@NotNull Player player) {
|
public void sendChunk(@NotNull Player player) {
|
||||||
if (!isLoaded()) return;
|
if (!isLoaded()) return;
|
||||||
player.sendPacket(lightCache.retrieve());
|
player.sendPackets(lightCache, chunkCache);
|
||||||
player.sendPacket(chunkCache.retrieve());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendChunk() {
|
public void sendChunk() {
|
||||||
if (!isLoaded()) return;
|
if (!isLoaded()) return;
|
||||||
if (getViewers().isEmpty()) return;
|
if (getViewers().isEmpty()) return;
|
||||||
sendPacketToViewers(lightCache.retrieve());
|
sendPacketsToViewers(lightCache, chunkCache);
|
||||||
sendPacketToViewers(chunkCache.retrieve());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package net.minestom.server.message;
|
package net.minestom.server.message;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
@ -10,6 +8,10 @@ import net.minestom.server.utils.PacketUtils;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to handle client chat settings.
|
* Utility class to handle client chat settings.
|
||||||
*/
|
*/
|
||||||
@ -79,7 +81,7 @@ public class Messenger {
|
|||||||
* @param player the player
|
* @param player the player
|
||||||
*/
|
*/
|
||||||
public static void sendRejectionMessage(@NotNull Player player) {
|
public static void sendRejectionMessage(@NotNull Player player) {
|
||||||
player.getPlayerConnection().sendPacket(CANNOT_SEND_PACKET, false);
|
player.getPlayerConnection().sendPacket(CANNOT_SEND_PACKET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,33 +9,41 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public final class CachedPacket {
|
public final class CachedPacket implements SendablePacket {
|
||||||
private static final AtomicIntegerFieldUpdater<CachedPacket> UPDATER = AtomicIntegerFieldUpdater.newUpdater(CachedPacket.class, "updated");
|
private static final AtomicIntegerFieldUpdater<CachedPacket> UPDATER = AtomicIntegerFieldUpdater.newUpdater(CachedPacket.class, "updated");
|
||||||
private final Supplier<ServerPacket> supplier;
|
private final Supplier<ServerPacket> packetSupplier;
|
||||||
// 0 means that the reference needs to be updated
|
// 0 means that the reference needs to be updated
|
||||||
// Anything else (currently 1) means that the packet is up-to-date
|
// Anything else (currently 1) means that the packet is up-to-date
|
||||||
private volatile int updated = 0;
|
private volatile int updated = 0;
|
||||||
private SoftReference<FramedPacket> packet;
|
private SoftReference<FramedPacket> packet;
|
||||||
|
|
||||||
public CachedPacket(@NotNull Supplier<@NotNull ServerPacket> supplier) {
|
public CachedPacket(@NotNull Supplier<@NotNull ServerPacket> packetSupplier) {
|
||||||
this.supplier = supplier;
|
this.packetSupplier = packetSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedPacket(@NotNull ServerPacket packet) {
|
||||||
|
this(() -> packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidate() {
|
public void invalidate() {
|
||||||
this.updated = 0;
|
this.updated = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NotNull ServerPacket packet() {
|
||||||
|
return packetSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
public @NotNull FramedPacket retrieve() {
|
public @NotNull FramedPacket retrieve() {
|
||||||
if (!PacketUtils.CACHED_PACKET) {
|
if (!PacketUtils.CACHED_PACKET) {
|
||||||
// TODO: Using a local buffer may be possible
|
// TODO: Using a local buffer may be possible
|
||||||
return PacketUtils.allocateTrimmedPacket(supplier.get());
|
return PacketUtils.allocateTrimmedPacket(packet());
|
||||||
}
|
}
|
||||||
SoftReference<FramedPacket> ref;
|
SoftReference<FramedPacket> ref;
|
||||||
FramedPacket cache;
|
FramedPacket cache;
|
||||||
if (updated == 0 ||
|
if (updated == 0 ||
|
||||||
((ref = packet) == null ||
|
((ref = packet) == null ||
|
||||||
(cache = ref.get()) == null)) {
|
(cache = ref.get()) == null)) {
|
||||||
cache = PacketUtils.allocateTrimmedPacket(supplier.get());
|
cache = PacketUtils.allocateTrimmedPacket(packet());
|
||||||
this.packet = new SoftReference<>(cache);
|
this.packet = new SoftReference<>(cache);
|
||||||
UPDATER.compareAndSet(this, 0, 1);
|
UPDATER.compareAndSet(this, 0, 1);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import java.nio.ByteBuffer;
|
|||||||
*/
|
*/
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public record FramedPacket(@NotNull ServerPacket packet,
|
public record FramedPacket(@NotNull ServerPacket packet,
|
||||||
@NotNull ByteBuffer body) {
|
@NotNull ByteBuffer body) implements SendablePacket {
|
||||||
|
|
||||||
public FramedPacket {
|
public FramedPacket {
|
||||||
body = body.position(0).asReadOnlyBuffer();
|
body = body.position(0).asReadOnlyBuffer();
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.minestom.server.network.packet.server;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public sealed interface SendablePacket
|
||||||
|
permits ServerPacket, CachedPacket, FramedPacket {
|
||||||
|
}
|
@ -7,9 +7,9 @@ import net.minestom.server.utils.binary.Writeable;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a packet which can be sent to a player using {@link PlayerConnection#sendPacket(ServerPacket)}.
|
* Represents a packet which can be sent to a player using {@link PlayerConnection#sendPacket(SendablePacket)}.
|
||||||
*/
|
*/
|
||||||
public interface ServerPacket extends Readable, Writeable {
|
public non-sealed interface ServerPacket extends Readable, Writeable, SendablePacket {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void read(@NotNull BinaryReader reader) {
|
default void read(@NotNull BinaryReader reader) {
|
||||||
|
@ -3,6 +3,10 @@ package net.minestom.server.network.player;
|
|||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
||||||
|
import net.minestom.server.entity.fakeplayer.FakePlayerController;
|
||||||
|
import net.minestom.server.network.packet.server.CachedPacket;
|
||||||
|
import net.minestom.server.network.packet.server.FramedPacket;
|
||||||
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -13,9 +17,17 @@ import java.net.SocketAddress;
|
|||||||
public class FakePlayerConnection extends PlayerConnection {
|
public class FakePlayerConnection extends PlayerConnection {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating) {
|
public void sendPacket(@NotNull SendablePacket packet) {
|
||||||
if (shouldSendPacket(serverPacket)) {
|
FakePlayerController controller = getFakePlayer().getController();
|
||||||
getFakePlayer().getController().consumePacket(serverPacket);
|
if (packet instanceof ServerPacket serverPacket) {
|
||||||
|
if (!shouldSendPacket(serverPacket)) return;
|
||||||
|
controller.consumePacket(serverPacket);
|
||||||
|
} else if (packet instanceof FramedPacket framedPacket) {
|
||||||
|
controller.consumePacket(framedPacket.packet());
|
||||||
|
} else if (packet instanceof CachedPacket cachedPacket) {
|
||||||
|
controller.consumePacket(cachedPacket.packet());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unknown packet type: " + packet.getClass().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,13 +8,14 @@ import net.minestom.server.listener.manager.PacketListenerManager;
|
|||||||
import net.minestom.server.listener.manager.ServerPacketConsumer;
|
import net.minestom.server.listener.manager.ServerPacketConsumer;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.server.FramedPacket;
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@ -85,26 +86,21 @@ public abstract class PlayerConnection {
|
|||||||
* <p>
|
* <p>
|
||||||
* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers.
|
* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers.
|
||||||
*
|
*
|
||||||
* @param serverPacket the packet to send
|
* @param packet the packet to send
|
||||||
* @see #shouldSendPacket(ServerPacket)
|
* @see #shouldSendPacket(ServerPacket)
|
||||||
*/
|
*/
|
||||||
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
public abstract void sendPacket(@NotNull SendablePacket packet);
|
||||||
this.sendPacket(serverPacket, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes the packet and send it to the client, optionally skipping the translation phase.
|
|
||||||
* <p>
|
|
||||||
* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers.
|
|
||||||
*
|
|
||||||
* @param serverPacket the packet to send
|
|
||||||
* @see #shouldSendPacket(ServerPacket)
|
|
||||||
*/
|
|
||||||
public abstract void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating);
|
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public void sendPacket(@NotNull FramedPacket framedPacket) {
|
public void sendPackets(@NotNull SendablePacket... packets) {
|
||||||
this.sendPacket(framedPacket.packet());
|
for (SendablePacket p : packets) {
|
||||||
|
sendPacket(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public void sendPackets(@NotNull Collection<SendablePacket> packet) {
|
||||||
|
packet.forEach(this::sendPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,9 +8,7 @@ import net.minestom.server.entity.PlayerSkin;
|
|||||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.PacketProcessor;
|
import net.minestom.server.network.PacketProcessor;
|
||||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
import net.minestom.server.network.packet.server.*;
|
||||||
import net.minestom.server.network.packet.server.FramedPacket;
|
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
|
||||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||||
import net.minestom.server.network.socket.Worker;
|
import net.minestom.server.network.socket.Worker;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
@ -52,8 +50,8 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
private final SocketChannel channel;
|
private final SocketChannel channel;
|
||||||
private SocketAddress remoteAddress;
|
private SocketAddress remoteAddress;
|
||||||
|
|
||||||
private boolean encrypted = false;
|
private volatile boolean encrypted = false;
|
||||||
private boolean compressed = false;
|
private volatile boolean compressed = false;
|
||||||
|
|
||||||
//Could be null. Only used for Mojang Auth
|
//Could be null. Only used for Mojang Auth
|
||||||
private byte[] nonce = new byte[4];
|
private byte[] nonce = new byte[4];
|
||||||
@ -74,8 +72,6 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
private UUID bungeeUuid;
|
private UUID bungeeUuid;
|
||||||
private PlayerSkin bungeeSkin;
|
private PlayerSkin bungeeSkin;
|
||||||
|
|
||||||
private final Object bufferLock = new Object();
|
|
||||||
private final Object flushLock = new Object();
|
|
||||||
private final List<BinaryBuffer> waitingBuffers = new ArrayList<>();
|
private final List<BinaryBuffer> waitingBuffers = new ArrayList<>();
|
||||||
private final AtomicReference<BinaryBuffer> tickBuffer = new AtomicReference<>(PooledBuffers.get());
|
private final AtomicReference<BinaryBuffer> tickBuffer = new AtomicReference<>(PooledBuffers.get());
|
||||||
private volatile BinaryBuffer cacheBuffer;
|
private volatile BinaryBuffer cacheBuffer;
|
||||||
@ -176,11 +172,9 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
*/
|
*/
|
||||||
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
||||||
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
||||||
synchronized (bufferLock) {
|
this.decryptCipher = MojangCrypt.getCipher(2, secretKey);
|
||||||
this.decryptCipher = MojangCrypt.getCipher(2, secretKey);
|
this.encryptCipher = MojangCrypt.getCipher(1, secretKey);
|
||||||
this.encryptCipher = MojangCrypt.getCipher(1, secretKey);
|
this.encrypted = true;
|
||||||
this.encrypted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,72 +187,18 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||||
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
|
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
|
||||||
writeAndFlush(new SetCompressionPacket(threshold));
|
writeAndFlush(new SetCompressionPacket(threshold));
|
||||||
synchronized (bufferLock) {
|
this.compressed = true;
|
||||||
this.compressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a packet to the connection channel.
|
|
||||||
* <p>
|
|
||||||
* All packets are flushed during {@link net.minestom.server.entity.Player#update(long)}.
|
|
||||||
*
|
|
||||||
* @param serverPacket the packet to write
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating) {
|
|
||||||
if (!channel.isConnected()) return;
|
|
||||||
if (shouldSendPacket(serverPacket)) {
|
|
||||||
final Player player = getPlayer();
|
|
||||||
if (player != null) {
|
|
||||||
// Flush happen during #update()
|
|
||||||
if ((MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && !skipTranslating) && serverPacket instanceof ComponentHoldingServerPacket) {
|
|
||||||
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component ->
|
|
||||||
GlobalTranslator.render(component, Objects.requireNonNullElseGet(player.getLocale(), MinestomAdventure::getDefaultLocale)));
|
|
||||||
}
|
|
||||||
writePacket(serverPacket);
|
|
||||||
} else {
|
|
||||||
// Player is probably not logged yet
|
|
||||||
writeAndFlush(serverPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacket(@NotNull FramedPacket framedPacket) {
|
public void sendPacket(@NotNull SendablePacket packet) {
|
||||||
write(framedPacket.body());
|
final boolean compressed = this.compressed;
|
||||||
|
this.worker.queue().offer(() -> writePacketSync(packet, compressed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public void write(@NotNull ByteBuffer buffer, int index, int length) {
|
public void write(@NotNull ByteBuffer buffer, int index, int length) {
|
||||||
synchronized (bufferLock) {
|
this.worker.queue().offer(() -> writeBufferSync(buffer, index, length));
|
||||||
if (encrypted) { // Encryption support
|
|
||||||
ByteBuffer output = PacketUtils.localBuffer();
|
|
||||||
try {
|
|
||||||
this.encryptCipher.update(buffer.slice(index, length), output);
|
|
||||||
buffer = output.flip();
|
|
||||||
index = 0;
|
|
||||||
} catch (ShortBufferException e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryBuffer localBuffer = tickBuffer.getPlain();
|
|
||||||
final int capacity = localBuffer.capacity();
|
|
||||||
if (length <= capacity) {
|
|
||||||
if (!localBuffer.canWrite(length)) localBuffer = updateLocalBuffer();
|
|
||||||
localBuffer.write(buffer, index, length);
|
|
||||||
} else {
|
|
||||||
final int bufferCount = length / capacity + 1;
|
|
||||||
for (int i = 0; i < bufferCount; i++) {
|
|
||||||
final int sliceStart = i * capacity;
|
|
||||||
final int sliceLength = Math.min(length, sliceStart + capacity) - sliceStart;
|
|
||||||
if (!localBuffer.canWrite(sliceLength)) localBuffer = updateLocalBuffer();
|
|
||||||
localBuffer.write(buffer, sliceStart, sliceLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@ -266,58 +206,17 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
write(buffer, buffer.position(), buffer.remaining());
|
write(buffer, buffer.position(), buffer.remaining());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writePacket(@NotNull ServerPacket packet) {
|
|
||||||
write(PacketUtils.createFramedPacket(packet, compressed));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeAndFlush(@NotNull ServerPacket packet) {
|
public void writeAndFlush(@NotNull ServerPacket packet) {
|
||||||
synchronized (bufferLock) {
|
final boolean compressed = this.compressed;
|
||||||
writePacket(packet);
|
this.worker.queue().offer(() -> {
|
||||||
flush();
|
writeServerPacketSync(packet, compressed);
|
||||||
}
|
flushSync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush() {
|
||||||
try {
|
this.worker.queue().offer(this::flushSync);
|
||||||
if (!channel.isConnected())
|
|
||||||
throw new ClosedChannelException();
|
|
||||||
synchronized (bufferLock) {
|
|
||||||
try {
|
|
||||||
updateLocalBuffer();
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
this.waitingBuffers.clear();
|
|
||||||
System.gc(); // Explicit gc forcing buffers to be collected
|
|
||||||
throw new ClosedChannelException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronized (flushLock) {
|
|
||||||
try {
|
|
||||||
// Write as much as possible from the waiting list
|
|
||||||
Iterator<BinaryBuffer> iterator = waitingBuffers.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
BinaryBuffer waitingBuffer = iterator.next();
|
|
||||||
if (!waitingBuffer.writeChannel(channel)) break;
|
|
||||||
iterator.remove();
|
|
||||||
PooledBuffers.add(waitingBuffer);
|
|
||||||
}
|
|
||||||
} catch (IOException e) { // Couldn't write to the socket
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
throw new ClosedChannelException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ClosedChannelException e) {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BinaryBuffer updateLocalBuffer() {
|
|
||||||
synchronized (flushLock) {
|
|
||||||
BinaryBuffer newBuffer = PooledBuffers.get();
|
|
||||||
this.waitingBuffers.add(tickBuffer.getPlain());
|
|
||||||
this.tickBuffer.setPlain(newBuffer);
|
|
||||||
return newBuffer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -339,7 +238,7 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
this.worker.disconnect(this, channel);
|
this.worker.queue().offer(() -> this.worker.disconnect(this, channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull SocketChannel getChannel() {
|
public @NotNull SocketChannel getChannel() {
|
||||||
@ -474,4 +373,102 @@ public class PlayerSocketConnection extends PlayerConnection {
|
|||||||
public void setNonce(byte[] nonce) {
|
public void setNonce(byte[] nonce) {
|
||||||
this.nonce = nonce;
|
this.nonce = nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writePacketSync(SendablePacket packet, boolean compressed) {
|
||||||
|
if (!channel.isConnected()) return;
|
||||||
|
if (packet instanceof ServerPacket serverPacket) {
|
||||||
|
writeServerPacketSync(serverPacket, compressed);
|
||||||
|
} else if (packet instanceof FramedPacket framedPacket) {
|
||||||
|
writeFramedPacketSync(framedPacket);
|
||||||
|
} else if (packet instanceof CachedPacket cachedPacket) {
|
||||||
|
writeFramedPacketSync(cachedPacket.retrieve());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unknown packet type: " + packet.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeServerPacketSync(ServerPacket serverPacket, boolean compressed) {
|
||||||
|
if (!shouldSendPacket(serverPacket)) return;
|
||||||
|
final Player player = getPlayer();
|
||||||
|
if (player != null) {
|
||||||
|
if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && serverPacket instanceof ComponentHoldingServerPacket) {
|
||||||
|
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component ->
|
||||||
|
GlobalTranslator.render(component, Objects.requireNonNullElseGet(player.getLocale(), MinestomAdventure::getDefaultLocale)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeBufferSync(PacketUtils.createFramedPacket(serverPacket, compressed));
|
||||||
|
if (player == null) flushSync(); // Player is probably not logged yet
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeFramedPacketSync(FramedPacket framedPacket) {
|
||||||
|
writeBufferSync(framedPacket.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBufferSync(@NotNull ByteBuffer buffer, int index, int length) {
|
||||||
|
if (encrypted) { // Encryption support
|
||||||
|
ByteBuffer output = PacketUtils.localBuffer();
|
||||||
|
try {
|
||||||
|
this.encryptCipher.update(buffer.slice(index, length), output);
|
||||||
|
buffer = output.flip();
|
||||||
|
index = 0;
|
||||||
|
} catch (ShortBufferException e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryBuffer localBuffer = tickBuffer.getPlain();
|
||||||
|
final int capacity = localBuffer.capacity();
|
||||||
|
if (length <= capacity) {
|
||||||
|
if (!localBuffer.canWrite(length)) localBuffer = updateLocalBuffer();
|
||||||
|
localBuffer.write(buffer, index, length);
|
||||||
|
} else {
|
||||||
|
final int bufferCount = length / capacity + 1;
|
||||||
|
for (int i = 0; i < bufferCount; i++) {
|
||||||
|
final int sliceStart = i * capacity;
|
||||||
|
final int sliceLength = Math.min(length, sliceStart + capacity) - sliceStart;
|
||||||
|
if (!localBuffer.canWrite(sliceLength)) localBuffer = updateLocalBuffer();
|
||||||
|
localBuffer.write(buffer, sliceStart, sliceLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBufferSync(@NotNull ByteBuffer buffer) {
|
||||||
|
writeBufferSync(buffer, buffer.position(), buffer.remaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushSync() {
|
||||||
|
try {
|
||||||
|
if (!channel.isConnected()) throw new ClosedChannelException();
|
||||||
|
try {
|
||||||
|
updateLocalBuffer();
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
this.waitingBuffers.clear();
|
||||||
|
System.gc(); // Explicit gc forcing buffers to be collected
|
||||||
|
throw new ClosedChannelException();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Write as much as possible from the waiting list
|
||||||
|
Iterator<BinaryBuffer> iterator = waitingBuffers.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
BinaryBuffer waitingBuffer = iterator.next();
|
||||||
|
if (!waitingBuffer.writeChannel(channel)) break;
|
||||||
|
iterator.remove();
|
||||||
|
PooledBuffers.add(waitingBuffer);
|
||||||
|
}
|
||||||
|
} catch (IOException e) { // Couldn't write to the socket
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
throw new ClosedChannelException();
|
||||||
|
}
|
||||||
|
} catch (ClosedChannelException e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BinaryBuffer updateLocalBuffer() {
|
||||||
|
BinaryBuffer newBuffer = PooledBuffers.get();
|
||||||
|
this.waitingBuffers.add(tickBuffer.getPlain());
|
||||||
|
this.tickBuffer.setPlain(newBuffer);
|
||||||
|
return newBuffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.minestom.server.network.PacketProcessor;
|
|||||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||||
import net.minestom.server.thread.MinestomThread;
|
import net.minestom.server.thread.MinestomThread;
|
||||||
import net.minestom.server.utils.binary.BinaryBuffer;
|
import net.minestom.server.utils.binary.BinaryBuffer;
|
||||||
|
import org.jctools.queues.MpscUnboundedArrayQueue;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -15,6 +16,7 @@ import java.nio.channels.SelectionKey;
|
|||||||
import java.nio.channels.Selector;
|
import java.nio.channels.Selector;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -29,7 +31,7 @@ public final class Worker extends MinestomThread {
|
|||||||
private final Map<SocketChannel, PlayerSocketConnection> connectionMap = new ConcurrentHashMap<>();
|
private final Map<SocketChannel, PlayerSocketConnection> connectionMap = new ConcurrentHashMap<>();
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final PacketProcessor packetProcessor;
|
private final PacketProcessor packetProcessor;
|
||||||
|
private final MpscUnboundedArrayQueue<Runnable> queue = new MpscUnboundedArrayQueue<>(1024);
|
||||||
private final AtomicBoolean flush = new AtomicBoolean();
|
private final AtomicBoolean flush = new AtomicBoolean();
|
||||||
|
|
||||||
public Worker(Server server, PacketProcessor packetProcessor) throws IOException {
|
public Worker(Server server, PacketProcessor packetProcessor) throws IOException {
|
||||||
@ -42,9 +44,10 @@ public final class Worker extends MinestomThread {
|
|||||||
public void run() {
|
public void run() {
|
||||||
while (server.isOpen()) {
|
while (server.isOpen()) {
|
||||||
try {
|
try {
|
||||||
|
this.queue.drain(Runnable::run);
|
||||||
// Flush all connections if needed
|
// Flush all connections if needed
|
||||||
if (flush.compareAndSet(true, false)) {
|
if (flush.compareAndSet(true, false)) {
|
||||||
connectionMap.values().forEach(PlayerSocketConnection::flush);
|
connectionMap.values().forEach(PlayerSocketConnection::flushSync);
|
||||||
}
|
}
|
||||||
// Wait for an event
|
// Wait for an event
|
||||||
this.selector.select(key -> {
|
this.selector.select(key -> {
|
||||||
@ -105,6 +108,10 @@ public final class Worker extends MinestomThread {
|
|||||||
this.selector.wakeup();
|
this.selector.wakeup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Queue<Runnable> queue() {
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains objects that we can be shared across all the connection of a {@link Worker worker}.
|
* Contains objects that we can be shared across all the connection of a {@link Worker worker}.
|
||||||
*/
|
*/
|
||||||
|
@ -9,13 +9,13 @@ import net.kyori.adventure.audience.Audience;
|
|||||||
import net.kyori.adventure.audience.ForwardingAudience;
|
import net.kyori.adventure.audience.ForwardingAudience;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.Viewable;
|
import net.minestom.server.Viewable;
|
||||||
import net.minestom.server.adventure.MinestomAdventure;
|
|
||||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
import net.minestom.server.network.packet.server.CachedPacket;
|
||||||
import net.minestom.server.network.packet.server.FramedPacket;
|
import net.minestom.server.network.packet.server.FramedPacket;
|
||||||
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||||
@ -24,7 +24,6 @@ import net.minestom.server.utils.binary.BinaryBuffer;
|
|||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
import net.minestom.server.utils.binary.PooledBuffers;
|
import net.minestom.server.utils.binary.PooledBuffers;
|
||||||
import net.minestom.server.utils.cache.LocalCache;
|
import net.minestom.server.utils.cache.LocalCache;
|
||||||
import net.minestom.server.utils.callback.validator.PlayerValidator;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -33,6 +32,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,45 +105,26 @@ public final class PacketUtils {
|
|||||||
* <p>
|
* <p>
|
||||||
* Can drastically improve performance since the packet will not have to be processed as much.
|
* Can drastically improve performance since the packet will not have to be processed as much.
|
||||||
*
|
*
|
||||||
* @param players the players to send the packet to
|
* @param players the players to send the packet to
|
||||||
* @param packet the packet to send to the players
|
* @param packet the packet to send to the players
|
||||||
* @param playerValidator optional callback to check if a specify player of {@code players} should receive the packet
|
* @param predicate predicate to ignore specific players
|
||||||
*/
|
*/
|
||||||
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet,
|
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet,
|
||||||
@NotNull PlayerValidator playerValidator) {
|
@NotNull Predicate<Player> predicate) {
|
||||||
if (players.isEmpty())
|
if (players.isEmpty()) return;
|
||||||
return;
|
if (!PACKET_LISTENER_MANAGER.processServerPacket(packet, players)) return;
|
||||||
// work out if the packet needs to be sent individually due to server-side translating
|
// work out if the packet needs to be sent individually due to server-side translating
|
||||||
boolean needsTranslating = false;
|
final SendablePacket sendablePacket = GROUPED_PACKET ? new CachedPacket(packet) : packet;
|
||||||
if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && packet instanceof ComponentHoldingServerPacket) {
|
players.forEach(player -> {
|
||||||
needsTranslating = ComponentUtils.areAnyTranslatable(((ComponentHoldingServerPacket) packet).components());
|
if (predicate.test(player)) player.sendPacket(sendablePacket);
|
||||||
}
|
});
|
||||||
if (GROUPED_PACKET && !needsTranslating) {
|
|
||||||
// Send grouped packet...
|
|
||||||
if (!PACKET_LISTENER_MANAGER.processServerPacket(packet, players))
|
|
||||||
return;
|
|
||||||
final FramedPacket framedPacket = new FramedPacket(packet, createFramedPacket(packet));
|
|
||||||
// Send packet to all players
|
|
||||||
players.forEach(player -> {
|
|
||||||
if (!player.isOnline() || !playerValidator.isValid(player))
|
|
||||||
return;
|
|
||||||
player.sendPacket(framedPacket);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Write the same packet for each individual players
|
|
||||||
players.forEach(player -> {
|
|
||||||
if (!player.isOnline() || !playerValidator.isValid(player))
|
|
||||||
return;
|
|
||||||
player.getPlayerConnection().sendPacket(packet, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as {@link #sendGroupedPacket(Collection, ServerPacket, PlayerValidator)}
|
* Same as {@link #sendGroupedPacket(Collection, ServerPacket, Predicate)}
|
||||||
* but with the player validator sets to null.
|
* but with the player validator sets to null.
|
||||||
*
|
*
|
||||||
* @see #sendGroupedPacket(Collection, ServerPacket, PlayerValidator)
|
* @see #sendGroupedPacket(Collection, ServerPacket, Predicate)
|
||||||
*/
|
*/
|
||||||
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet) {
|
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet) {
|
||||||
sendGroupedPacket(players, packet, player -> true);
|
sendGroupedPacket(players, packet, player -> true);
|
||||||
@ -274,13 +255,15 @@ public final class PacketUtils {
|
|||||||
|
|
||||||
private void process(Viewable viewable) {
|
private void process(Viewable viewable) {
|
||||||
if (buffer.writerOffset() == 0) return;
|
if (buffer.writerOffset() == 0) return;
|
||||||
viewable.getViewers().forEach(this::processPlayer);
|
ByteBuffer copy = ByteBuffer.allocateDirect(buffer.writerOffset());
|
||||||
|
copy.put(buffer.asByteBuffer(0, copy.capacity()));
|
||||||
|
viewable.getViewers().forEach(player -> processPlayer(player, copy));
|
||||||
this.buffer.clear();
|
this.buffer.clear();
|
||||||
this.entityIdMap.clear();
|
this.entityIdMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPlayer(Player player) {
|
private void processPlayer(Player player, ByteBuffer buffer) {
|
||||||
final int size = buffer.writerOffset();
|
final int size = buffer.limit();
|
||||||
final PlayerConnection connection = player.getPlayerConnection();
|
final PlayerConnection connection = player.getPlayerConnection();
|
||||||
final LongArrayList pairs = entityIdMap.get(player.getEntityId());
|
final LongArrayList pairs = entityIdMap.get(player.getEntityId());
|
||||||
if (pairs != null) {
|
if (pairs != null) {
|
||||||
@ -290,20 +273,16 @@ public final class PacketUtils {
|
|||||||
for (int i = 0; i < pairs.size(); ++i) {
|
for (int i = 0; i < pairs.size(); ++i) {
|
||||||
final long offsets = elements[i];
|
final long offsets = elements[i];
|
||||||
final int start = (int) (offsets >> 32);
|
final int start = (int) (offsets >> 32);
|
||||||
if (start != lastWrite) writeTo(connection, lastWrite, start - lastWrite);
|
if (start != lastWrite) writeTo(connection, buffer, lastWrite, start - lastWrite);
|
||||||
lastWrite = (int) offsets; // End = last 32 bits
|
lastWrite = (int) offsets; // End = last 32 bits
|
||||||
}
|
}
|
||||||
if (size != lastWrite) writeTo(connection, lastWrite, size - lastWrite);
|
if (size != lastWrite) writeTo(connection, buffer, lastWrite, size - lastWrite);
|
||||||
} else {
|
} else {
|
||||||
// Write all
|
// Write all
|
||||||
writeTo(connection, 0, size);
|
writeTo(connection, buffer, 0, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeTo(PlayerConnection connection, int offset, int length) {
|
|
||||||
writeTo(connection, buffer.asByteBuffer(), offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeTo(PlayerConnection connection, ByteBuffer buffer, int offset, int length) {
|
private static void writeTo(PlayerConnection connection, ByteBuffer buffer, int offset, int length) {
|
||||||
if (connection instanceof PlayerSocketConnection socketConnection) {
|
if (connection instanceof PlayerSocketConnection socketConnection) {
|
||||||
socketConnection.write(buffer, offset, length);
|
socketConnection.write(buffer, offset, length);
|
||||||
|
Loading…
Reference in New Issue
Block a user