Batch movement packets

This commit is contained in:
TheMode 2021-08-25 09:01:13 +02:00
parent 6520855418
commit 2ae0c0bbcd
2 changed files with 66 additions and 56 deletions

View File

@ -34,6 +34,7 @@ import net.minestom.server.potion.TimedPotion;
import net.minestom.server.tag.Tag; import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagHandler;
import net.minestom.server.thread.ThreadProvider; import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator; import net.minestom.server.utils.block.BlockIterator;
import net.minestom.server.utils.chunk.ChunkUtils; 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 distanceY = Math.abs(position.y() - lastSyncedPosition.y());
final double distanceZ = Math.abs(position.z() - lastSyncedPosition.z()); final double distanceZ = Math.abs(position.z() - lastSyncedPosition.z());
final boolean positionChange = (distanceX + distanceY + distanceZ) > 0; 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) { 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) { } else if (positionChange && viewChange) {
sendPacketToViewers(EntityPositionAndRotationPacket.getPacket(getEntityId(), position, PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, isOnGround())); lastSyncedPosition, isOnGround()), player);
// Fix head rotation // Fix head rotation
sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw())); PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), player);
} else if (positionChange) { } else if (positionChange) {
sendPacketToViewers(EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround)); PacketUtils.prepareViewablePacket(chunk, EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround), player);
} else if (viewChange) { } else if (viewChange) {
sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw())); PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), player);
sendPacketToViewers(new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround)); PacketUtils.prepareViewablePacket(chunk, new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround), player);
} }
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis(); this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
this.lastSyncedPosition = position; this.lastSyncedPosition = position;

View File

@ -7,7 +7,6 @@ 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.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.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.FramedPacket; import net.minestom.server.network.packet.FramedPacket;
@ -25,9 +24,7 @@ import org.jetbrains.annotations.Nullable;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collection; import java.util.*;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.zip.Deflater; import java.util.zip.Deflater;
@ -236,21 +233,19 @@ public final class PacketUtils {
} }
} }
private static volatile Map<Viewable, ViewableStorage> VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>(); private static final Map<Viewable, ViewableStorage> VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>();
private static class ViewableStorage { private static class ViewableStorage {
private final Viewable viewable; private final Viewable viewable;
private final Map<Integer, Entry> entries = new ConcurrentHashMap<>(); private final Entry entry = new ViewableStorage.Entry();
private ViewableStorage(Viewable viewable) { private ViewableStorage(Viewable viewable) {
this.viewable = viewable; this.viewable = viewable;
} }
private synchronized void append(PlayerConnection playerConnection, ServerPacket serverPacket) { private synchronized void append(PlayerConnection playerConnection, ServerPacket serverPacket) {
ViewableStorage.Entry entry = entries.computeIfAbsent(serverPacket.getId(), integer -> new ViewableStorage.Entry());
final boolean hasConnection = playerConnection != null; final boolean hasConnection = playerConnection != null;
var entityIdMap = entry.entityIdMap; var entityIdMap = entry.entityIdMap;
if (hasConnection && entityIdMap.containsKey(playerConnection)) return;
BinaryBuffer buffer = entry.buffer; BinaryBuffer buffer = entry.buffer;
final int start = buffer.writerOffset(); final int start = buffer.writerOffset();
@ -259,75 +254,86 @@ public final class PacketUtils {
final int end = buffer.writerOffset(); final int end = buffer.writerOffset();
if (hasConnection) { if (hasConnection) {
entityIdMap.put(playerConnection, IntIntPair.of(start, end)); List<IntIntPair> list = entityIdMap.computeIfAbsent(playerConnection, playerConnection1 -> new ArrayList<>());
list.add(IntIntPair.of(start, end));
} }
} }
private void process() { private synchronized void process() {
this.entries.forEach((integer, entry) -> { final var entityIdMap = entry.entityIdMap;
final var entityIdMap = entry.entityIdMap;
BinaryBuffer buffer = entry.buffer; BinaryBuffer buffer = entry.buffer;
final int readable = buffer.readableBytes(); final int readable = buffer.readableBytes();
final Set<Player> viewers = viewable.getViewers(); final Set<Player> viewers = viewable.getViewers();
if (viewers.isEmpty()) return; if (viewers.isEmpty()) return;
for (Player player : viewers) { for (Player player : viewers) {
PlayerConnection connection = player.getPlayerConnection(); PlayerConnection connection = player.getPlayerConnection();
Consumer<ByteBuffer> writer = connection instanceof PlayerSocketConnection Consumer<ByteBuffer> writer = connection instanceof PlayerSocketConnection
? ((PlayerSocketConnection) connection)::write : ? ((PlayerSocketConnection) connection)::write :
byteBuffer -> { byteBuffer -> {
// TODO for non-socket connection // TODO for non-socket connection
}; };
final var pair = entityIdMap.get(connection); final List<IntIntPair> pairs = entityIdMap.get(connection);
if (pair != null) { if (pairs != null) {
int lastWrite = 0;
for (var pair : pairs) {
final int start = pair.leftInt(); final int start = pair.leftInt();
final int end = pair.rightInt(); final int end = pair.rightInt();
if (start == 0) { if (start > lastWrite) {
writer.accept(buffer.asByteBuffer(end, readable - end)); ByteBuffer slice = buffer.asByteBuffer(lastWrite, start);
} else if (end == readable) { slice.position(slice.limit());
writer.accept(buffer.asByteBuffer(0, start)); writer.accept(slice);
} else {
writer.accept(buffer.asByteBuffer(0, start));
writer.accept(buffer.asByteBuffer(end, readable - end));
} }
} else { lastWrite = end;
ByteBuffer result = buffer.asByteBuffer(0, buffer.writerOffset());
result.position(result.limit());
writer.accept(result);
} }
// 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 { private static class Entry {
Map<PlayerConnection, IntIntPair> entityIdMap = new ConcurrentHashMap<>(); Map<PlayerConnection, List<IntIntPair>> entityIdMap = new ConcurrentHashMap<>();
BinaryBuffer buffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE); 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, public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket,
@Nullable Entity entity) { @Nullable Player player) {
if (entity != null && !entity.isAutoViewable()) { if (player != null && !player.isAutoViewable()) {
// Operation cannot be optimized // Operation cannot be optimized
entity.sendPacketToViewers(serverPacket); player.sendPacketToViewers(serverPacket);
return; 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 viewableStorage = VIEWABLE_STORAGE_MAP.computeIfAbsent(viewable, c -> new ViewableStorage(viewable));
viewableStorage.append(playerConnection, serverPacket); viewableStorage.append(playerConnection, serverPacket);
} }
public static void prepareGroupedPacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket) { public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket) {
prepareGroupedPacket(viewable, serverPacket, null); prepareViewablePacket(viewable, serverPacket, null);
} }
public static void flush() { public static void flush() {
final var map = VIEWABLE_STORAGE_MAP; for (ViewableStorage viewableStorage : VIEWABLE_STORAGE_MAP.values()) {
VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>();
for (ViewableStorage viewableStorage : map.values()) {
if (viewableStorage.entries.isEmpty()) continue;
viewableStorage.process(); viewableStorage.process();
} }
} }