Merge pull request #431 from Minestom/viewable-broadcast

Viewable packet broadcast
This commit is contained in:
TheMode 2021-09-04 14:11:43 +02:00 committed by GitHub
commit a5b6af34bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 12 deletions

View File

@ -9,6 +9,8 @@ import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.thread.SingleThreadProvider;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -81,9 +83,12 @@ public final class UpdateManager {
}
// Flush all waiting packets
for (Player player : connectionManager.getOnlinePlayers()) {
player.getPlayerConnection().flush();
}
AsyncUtils.runAsync(() -> {
PacketUtils.flush();
for (Player player : connectionManager.getOnlinePlayers()) {
player.getPlayerConnection().flush();
}
});
// Disable thread until next tick
LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime));

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;
@ -1162,18 +1163,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

@ -1,8 +1,10 @@
package net.minestom.server.utils;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
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.Player;
@ -13,16 +15,18 @@ import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection;
import net.minestom.server.network.socket.Server;
import net.minestom.server.utils.binary.BinaryBuffer;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.callback.validator.PlayerValidator;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.zip.Deflater;
/**
@ -35,6 +39,9 @@ public final class PacketUtils {
private static final LocalCache PACKET_BUFFER = LocalCache.get("packet-buffer", Server.SOCKET_BUFFER_SIZE);
private static final LocalCache COMPRESSION_CACHE = LocalCache.get("compression-buffer", Server.SOCKET_BUFFER_SIZE);
private static final Object VIEWABLE_PACKET_LOCK = new Object();
private static final Map<Viewable, ViewableStorage> VIEWABLE_STORAGE_MAP = new WeakHashMap<>();
private PacketUtils() {
}
@ -136,6 +143,35 @@ public final class PacketUtils {
sendGroupedPacket(MinecraftServer.getConnectionManager().getOnlinePlayers(), packet);
}
@ApiStatus.Experimental
public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket,
@Nullable Player player) {
if (player != null && !player.isAutoViewable()) {
// Operation cannot be optimized
player.sendPacketToViewers(serverPacket);
return;
}
ViewableStorage viewableStorage;
synchronized (VIEWABLE_PACKET_LOCK) {
viewableStorage = VIEWABLE_STORAGE_MAP.computeIfAbsent(viewable, ViewableStorage::new);
}
viewableStorage.append(serverPacket, player != null ? player.getPlayerConnection() : null);
}
@ApiStatus.Experimental
public static void prepareViewablePacket(@NotNull Viewable viewable, @NotNull ServerPacket serverPacket) {
prepareViewablePacket(viewable, serverPacket, null);
}
@ApiStatus.Internal
public static void flush() {
synchronized (VIEWABLE_PACKET_LOCK) {
for (ViewableStorage viewableStorage : VIEWABLE_STORAGE_MAP.values()) {
viewableStorage.process();
}
}
}
public static void writeFramedPacket(@NotNull ByteBuffer buffer,
@NotNull ServerPacket packet,
boolean compression) {
@ -234,4 +270,61 @@ public final class PacketUtils {
return cache.get().clear();
}
}
private static final class ViewableStorage {
private final Viewable viewable;
private final Map<PlayerConnection, List<IntIntPair>> entityIdMap = new HashMap<>();
private final BinaryBuffer buffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE);
private ViewableStorage(Viewable viewable) {
this.viewable = viewable;
}
private synchronized void append(ServerPacket serverPacket, PlayerConnection connection) {
final ByteBuffer framedPacket = createFramedPacket(serverPacket).flip();
if (!buffer.canWrite(framedPacket.limit())) process();
final int start = buffer.writerOffset();
this.buffer.write(framedPacket);
final int end = buffer.writerOffset();
if (connection != null) {
List<IntIntPair> list = entityIdMap.computeIfAbsent(connection, con -> new ArrayList<>());
list.add(IntIntPair.of(start, end));
}
}
private synchronized void process() {
for (Player player : viewable.getViewers()) {
PlayerConnection connection = player.getPlayerConnection();
Consumer<ByteBuffer> writer = connection instanceof PlayerSocketConnection
? ((PlayerSocketConnection) connection)::write :
byteBuffer -> {
// TODO for non-socket connection
};
int lastWrite = 0;
final List<IntIntPair> pairs = entityIdMap.get(connection);
if (pairs != null && !pairs.isEmpty()) {
for (IntIntPair pair : pairs) {
final int start = pair.leftInt();
if (start != lastWrite) {
ByteBuffer slice = buffer.asByteBuffer(lastWrite, start);
slice.position(slice.limit());
writer.accept(slice);
}
lastWrite = pair.rightInt();
}
}
// Write remaining
final int remaining = buffer.writerOffset() - lastWrite;
if (remaining > 0) {
ByteBuffer remainSlice = buffer.asByteBuffer(lastWrite, remaining);
remainSlice.position(remainSlice.limit());
writer.accept(remainSlice);
}
}
// Clear state
this.entityIdMap.clear();
this.buffer.clear();
}
}
}