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.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;

View File

@ -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, ViewableStorage> VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>();
private static final Map<Viewable, ViewableStorage> VIEWABLE_STORAGE_MAP = new ConcurrentHashMap<>();
private static class ViewableStorage {
private final Viewable viewable;
private final Map<Integer, Entry> 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<IntIntPair> 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<Player> viewers = viewable.getViewers();
if (viewers.isEmpty()) return;
for (Player player : viewers) {
PlayerConnection connection = player.getPlayerConnection();
Consumer<ByteBuffer> writer = connection instanceof PlayerSocketConnection
? ((PlayerSocketConnection) connection)::write :
byteBuffer -> {
// TODO for non-socket connection
};
final Set<Player> viewers = viewable.getViewers();
if (viewers.isEmpty()) return;
for (Player player : viewers) {
PlayerConnection connection = player.getPlayerConnection();
Consumer<ByteBuffer> writer = connection instanceof PlayerSocketConnection
? ((PlayerSocketConnection) connection)::write :
byteBuffer -> {
// TODO for non-socket connection
};
final var pair = entityIdMap.get(connection);
if (pair != null) {
final List<IntIntPair> 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<PlayerConnection, IntIntPair> entityIdMap = new ConcurrentHashMap<>();
Map<PlayerConnection, List<IntIntPair>> 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();
}
}