From 2ae0c0bbcd2bd190a66919c73b6b4a55e1de6ae9 Mon Sep 17 00:00:00 2001 From: TheMode Date: Wed, 25 Aug 2021 09:01:13 +0200 Subject: [PATCH] Batch movement packets --- .../net/minestom/server/entity/Entity.java | 18 +-- .../minestom/server/utils/PacketUtils.java | 104 +++++++++--------- 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 07f2218f4..36a7833c5 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -34,6 +34,7 @@ import net.minestom.server.potion.TimedPotion; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.thread.ThreadProvider; +import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.block.BlockIterator; import net.minestom.server.utils.chunk.ChunkUtils; @@ -1160,18 +1161,21 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler final double distanceY = Math.abs(position.y() - lastSyncedPosition.y()); final double distanceZ = Math.abs(position.z() - lastSyncedPosition.z()); final boolean positionChange = (distanceX + distanceY + distanceZ) > 0; + + final Player player = this instanceof Player ? (Player) this : null; + final Chunk chunk = getChunk(); if (distanceX > 8 || distanceY > 8 || distanceZ > 8) { - sendPacketToViewers(new EntityTeleportPacket(getEntityId(), position, isOnGround())); + PacketUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), player); } else if (positionChange && viewChange) { - sendPacketToViewers(EntityPositionAndRotationPacket.getPacket(getEntityId(), position, - lastSyncedPosition, isOnGround())); + PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position, + lastSyncedPosition, isOnGround()), player); // Fix head rotation - sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw())); + PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), player); } else if (positionChange) { - sendPacketToViewers(EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround)); + PacketUtils.prepareViewablePacket(chunk, EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround), player); } else if (viewChange) { - sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw())); - sendPacketToViewers(new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround)); + PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), player); + PacketUtils.prepareViewablePacket(chunk, new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround), player); } this.lastAbsoluteSynchronizationTime = System.currentTimeMillis(); this.lastSyncedPosition = position; diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index e82175b4b..2269399a7 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -7,7 +7,6 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.Viewable; import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.adventure.audience.PacketGroupingAudience; -import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.network.packet.FramedPacket; @@ -25,9 +24,7 @@ import org.jetbrains.annotations.Nullable; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.zip.Deflater; @@ -236,21 +233,19 @@ public final class PacketUtils { } } - private static volatile Map VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>(); + private static final Map VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>(); private static class ViewableStorage { private final Viewable viewable; - private final Map entries = new ConcurrentHashMap<>(); + private final Entry entry = new ViewableStorage.Entry(); private ViewableStorage(Viewable viewable) { this.viewable = viewable; } private synchronized void append(PlayerConnection playerConnection, ServerPacket serverPacket) { - ViewableStorage.Entry entry = entries.computeIfAbsent(serverPacket.getId(), integer -> new ViewableStorage.Entry()); final boolean hasConnection = playerConnection != null; var entityIdMap = entry.entityIdMap; - if (hasConnection && entityIdMap.containsKey(playerConnection)) return; BinaryBuffer buffer = entry.buffer; final int start = buffer.writerOffset(); @@ -259,75 +254,86 @@ public final class PacketUtils { final int end = buffer.writerOffset(); if (hasConnection) { - entityIdMap.put(playerConnection, IntIntPair.of(start, end)); + List list = entityIdMap.computeIfAbsent(playerConnection, playerConnection1 -> new ArrayList<>()); + list.add(IntIntPair.of(start, end)); } } - private void process() { - this.entries.forEach((integer, entry) -> { - final var entityIdMap = entry.entityIdMap; + private synchronized void process() { + final var entityIdMap = entry.entityIdMap; - BinaryBuffer buffer = entry.buffer; - final int readable = buffer.readableBytes(); + BinaryBuffer buffer = entry.buffer; + final int readable = buffer.readableBytes(); - final Set viewers = viewable.getViewers(); - if (viewers.isEmpty()) return; - for (Player player : viewers) { - PlayerConnection connection = player.getPlayerConnection(); - Consumer writer = connection instanceof PlayerSocketConnection - ? ((PlayerSocketConnection) connection)::write : - byteBuffer -> { - // TODO for non-socket connection - }; + final Set viewers = viewable.getViewers(); + if (viewers.isEmpty()) return; + for (Player player : viewers) { + PlayerConnection connection = player.getPlayerConnection(); + Consumer writer = connection instanceof PlayerSocketConnection + ? ((PlayerSocketConnection) connection)::write : + byteBuffer -> { + // TODO for non-socket connection + }; - final var pair = entityIdMap.get(connection); - if (pair != null) { + final List pairs = entityIdMap.get(connection); + if (pairs != null) { + int lastWrite = 0; + for (var pair : pairs) { final int start = pair.leftInt(); final int end = pair.rightInt(); - if (start == 0) { - writer.accept(buffer.asByteBuffer(end, readable - end)); - } else if (end == readable) { - writer.accept(buffer.asByteBuffer(0, start)); - } else { - writer.accept(buffer.asByteBuffer(0, start)); - writer.accept(buffer.asByteBuffer(end, readable - end)); + if (start > lastWrite) { + ByteBuffer slice = buffer.asByteBuffer(lastWrite, start); + slice.position(slice.limit()); + writer.accept(slice); } - } else { - ByteBuffer result = buffer.asByteBuffer(0, buffer.writerOffset()); - result.position(result.limit()); - writer.accept(result); + lastWrite = end; } + // Write remaining + final int remaining = readable - lastWrite; + if (remaining > 0) { + ByteBuffer remainSlice = buffer.asByteBuffer(lastWrite, remaining); + remainSlice.position(remaining); + writer.accept(remainSlice); + } + } else { + ByteBuffer result = buffer.asByteBuffer(0, buffer.writerOffset()); + result.position(result.limit()); + writer.accept(result); } - }); + } + + this.entry.reset(); } private static class Entry { - Map entityIdMap = new ConcurrentHashMap<>(); + Map> entityIdMap = new ConcurrentHashMap<>(); BinaryBuffer buffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE); + + void reset() { + this.entityIdMap.clear(); + this.buffer.clear(); + } } } - public static void prepareGroupedPacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket, - @Nullable Entity entity) { - if (entity != null && !entity.isAutoViewable()) { + public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket, + @Nullable Player player) { + if (player != null && !player.isAutoViewable()) { // Operation cannot be optimized - entity.sendPacketToViewers(serverPacket); + player.sendPacketToViewers(serverPacket); return; } - final PlayerConnection playerConnection = entity instanceof Player ? ((Player) entity).getPlayerConnection() : null; + final PlayerConnection playerConnection = player != null ? player.getPlayerConnection() : null; ViewableStorage viewableStorage = VIEWABLE_STORAGE_MAP.computeIfAbsent(viewable, c -> new ViewableStorage(viewable)); viewableStorage.append(playerConnection, serverPacket); } - public static void prepareGroupedPacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket) { - prepareGroupedPacket(viewable, serverPacket, null); + public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket) { + prepareViewablePacket(viewable, serverPacket, null); } public static void flush() { - final var map = VIEWABLE_STORAGE_MAP; - VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>(); - for (ViewableStorage viewableStorage : map.values()) { - if (viewableStorage.entries.isEmpty()) continue; + for (ViewableStorage viewableStorage : VIEWABLE_STORAGE_MAP.values()) { viewableStorage.process(); } }