mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-30 13:08:19 +01:00
Merge pull request #431 from Minestom/viewable-broadcast
Viewable packet broadcast
This commit is contained in:
commit
a5b6af34bd
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user