hytale-networking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hytale Networking API

Hytale 网络通信API

网络通信完整指南:处理数据包与客户端-服务器通信

何时使用本技能

Complete guide for handling network packets and client-server communication.
使用本技能的场景:
  • 向客户端发送数据
  • 接收客户端数据
  • 创建自定义数据包类型
  • 实现自定义UI同步
  • 处理实时玩家输入
  • 同步自定义游戏状态

When to use this skill

网络架构概述

Use this skill when:
  • Sending data to clients
  • Receiving data from clients
  • Creating custom packet types
  • Implementing custom UI synchronization
  • Handling real-time player input
  • Syncing custom game state
Hytale 采用基于数据包的网络通信系统:
Client <---> Server
   |           |
   Packet      Packet
   Encoder     Decoder
   |           |
   ByteBuf <-> ByteBuf

Network Architecture Overview

协议组件

Hytale uses a packet-based networking system:
Client <---> Server
   |           |
   Packet      Packet
   Encoder     Decoder
   |           |
   ByteBuf <-> ByteBuf
组件描述
Packet
带序列化功能的数据结构
PacketRegistry
映射数据包ID与类
PacketHandler
处理传入的数据包
PacketEncoder
将数据包序列化为字节
PacketDecoder
将字节反序列化为数据包

Protocol Components

数据包结构

数据包接口

ComponentDescription
Packet
Data structure with serialization
PacketRegistry
Maps packet IDs to classes
PacketHandler
Processes incoming packets
PacketEncoder
Serializes packets to bytes
PacketDecoder
Deserializes bytes to packets
所有数据包都实现
Packet
接口:
java
public interface Packet {
    int getId();
    void serialize(ByteBuf buffer);
    int computeSize();
}

Packet Structure

内置数据包分类

Packet Interface

All packets implement the
Packet
interface:
java
public interface Packet {
    int getId();
    void serialize(ByteBuf buffer);
    int computeSize();
}
分类方向示例
auth
双向AuthToken, ConnectAccept
connection
双向Connect, Disconnect, Ping
setup
服务器→客户端WorldSettings, AssetInitialize
player
客户端→服务器ClientMovement, MouseInteraction
entities
服务器→客户端EntityUpdates, PlayAnimation
world
服务器→客户端SetChunk, ServerSetBlock
inventory
双向MoveItemStack, SetActiveSlot
window
双向OpenWindow, CloseWindow
interface
服务器→客户端ChatMessage, Notification
interaction
双向SyncInteractionChains
camera
服务器→客户端CameraShakeEffect

Built-in Packet Categories

注册数据包处理器

SubPacketHandler 模式

CategoryDirectionExamples
auth
BidirectionalAuthToken, ConnectAccept
connection
BidirectionalConnect, Disconnect, Ping
setup
S→CWorldSettings, AssetInitialize
player
C→SClientMovement, MouseInteraction
entities
S→CEntityUpdates, PlayAnimation
world
S→CSetChunk, ServerSetBlock
inventory
BidirectionalMoveItemStack, SetActiveSlot
window
BidirectionalOpenWindow, CloseWindow
interface
S→CChatMessage, Notification
interaction
BidirectionalSyncInteractionChains
camera
S→CCameraShakeEffect
创建模块化数据包处理器:
java
public class MyPacketHandler implements SubPacketHandler {
    private final MyPlugin plugin;
    
    public MyPacketHandler(MyPlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    public void registerHandlers(IPacketHandler handler) {
        // Register by packet ID
        handler.registerHandler(108, this::handleClientMovement);
        handler.registerHandler(111, this::handleMouseInteraction);
        
        // Or by packet class
        handler.registerHandler(CustomPacket.ID, this::handleCustomPacket);
    }
    
    private void handleClientMovement(Packet packet) {
        ClientMovement movement = (ClientMovement) packet;
        Vector3d position = movement.getPosition();
        // Process movement
    }
    
    private void handleMouseInteraction(Packet packet) {
        MouseInteraction interaction = (MouseInteraction) packet;
        // Process mouse input
    }
}

Registering Packet Handlers

在插件中注册

SubPacketHandler Pattern

Create modular packet handlers:
java
public class MyPacketHandler implements SubPacketHandler {
    private final MyPlugin plugin;
    
    public MyPacketHandler(MyPlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    public void registerHandlers(IPacketHandler handler) {
        // Register by packet ID
        handler.registerHandler(108, this::handleClientMovement);
        handler.registerHandler(111, this::handleMouseInteraction);
        
        // Or by packet class
        handler.registerHandler(CustomPacket.ID, this::handleCustomPacket);
    }
    
    private void handleClientMovement(Packet packet) {
        ClientMovement movement = (ClientMovement) packet;
        Vector3d position = movement.getPosition();
        // Process movement
    }
    
    private void handleMouseInteraction(Packet packet) {
        MouseInteraction interaction = (MouseInteraction) packet;
        // Process mouse input
    }
}
java
@Override
protected void setup() {
    // Register packet handler with server manager
    ServerManager serverManager = HytaleServer.get().getServerManager();
    serverManager.registerSubPacketHandler(new MyPacketHandler(this));
}

Registering in Plugin

发送数据包

发送给单个玩家

java
@Override
protected void setup() {
    // Register packet handler with server manager
    ServerManager serverManager = HytaleServer.get().getServerManager();
    serverManager.registerSubPacketHandler(new MyPacketHandler(this));
}
java
public void sendToPlayer(Player player, Packet packet) {
    player.getConnection().send(packet);
}

Sending Packets

发送给多个玩家

Send to Single Player

java
public void sendToPlayer(Player player, Packet packet) {
    player.getConnection().send(packet);
}
java
public void sendToAll(Packet packet) {
    for (Player player : HytaleServer.get().getOnlinePlayers()) {
        player.getConnection().send(packet);
    }
}

public void sendToWorld(World world, Packet packet) {
    for (Player player : world.getPlayers()) {
        player.getConnection().send(packet);
    }
}

public void sendToNearby(Vector3d position, double radius, Packet packet) {
    for (Player player : HytaleServer.get().getOnlinePlayers()) {
        if (player.getPosition().distanceTo(position) <= radius) {
            player.getConnection().send(packet);
        }
    }
}

Send to Multiple Players

带回调的发送

java
public void sendToAll(Packet packet) {
    for (Player player : HytaleServer.get().getOnlinePlayers()) {
        player.getConnection().send(packet);
    }
}

public void sendToWorld(World world, Packet packet) {
    for (Player player : world.getPlayers()) {
        player.getConnection().send(packet);
    }
}

public void sendToNearby(Vector3d position, double radius, Packet packet) {
    for (Player player : HytaleServer.get().getOnlinePlayers()) {
        if (player.getPosition().distanceTo(position) <= radius) {
            player.getConnection().send(packet);
        }
    }
}
java
player.getConnection().send(packet).thenAccept(result -> {
    if (result.isSuccess()) {
        getLogger().atInfo().log("Packet sent successfully");
    } else {
        getLogger().atWarning().log("Packet failed to send: %s", result.getError());
    }
});

Send with Callback

内置数据包参考

聊天消息

java
player.getConnection().send(packet).thenAccept(result -> {
    if (result.isSuccess()) {
        getLogger().atInfo().log("Packet sent successfully");
    } else {
        getLogger().atWarning().log("Packet failed to send: %s", result.getError());
    }
});
java
// Send chat message to player
ChatMessage chatPacket = new ChatMessage(
    "Hello, World!",
    ChatMessage.Type.SYSTEM
);
player.getConnection().send(chatPacket);

Built-in Packets Reference

通知消息

Chat Message

java
// Send chat message to player
ChatMessage chatPacket = new ChatMessage(
    "Hello, World!",
    ChatMessage.Type.SYSTEM
);
player.getConnection().send(chatPacket);
java
// Send notification popup
Notification notification = new Notification(
    "Achievement Unlocked!",
    "You found the secret area",
    Notification.Type.SUCCESS,
    5000  // Duration in ms
);
player.getConnection().send(notification);

Notification

播放音效

java
// Send notification popup
Notification notification = new Notification(
    "Achievement Unlocked!",
    "You found the secret area",
    Notification.Type.SUCCESS,
    5000  // Duration in ms
);
player.getConnection().send(notification);
java
// Play sound at position
PlaySoundPacket sound = new PlaySoundPacket(
    "MyPlugin/Sounds/alert",
    position,
    1.0f,  // Volume
    1.0f   // Pitch
);
player.getConnection().send(sound);

Play Sound

实体状态更新

java
// Play sound at position
PlaySoundPacket sound = new PlaySoundPacket(
    "MyPlugin/Sounds/alert",
    position,
    1.0f,  // Volume
    1.0f   // Pitch
);
player.getConnection().send(sound);
java
// Send entity state update
EntityUpdates updates = new EntityUpdates(
    entityId,
    new HashMap<>() {{
        put("health", health);
        put("position", position);
    }}
);
sendToNearby(position, 64, updates);

Entity Updates

设置方块

java
// Send entity state update
EntityUpdates updates = new EntityUpdates(
    entityId,
    new HashMap<>() {{
        put("health", health);
        put("position", position);
    }}
);
sendToNearby(position, 64, updates);
java
// Update block on client
ServerSetBlock setBlock = new ServerSetBlock(
    position,
    blockTypeId,
    blockState
);
sendToWorld(world, setBlock);

Set Block

创建自定义数据包

定义数据包类

java
// Update block on client
ServerSetBlock setBlock = new ServerSetBlock(
    position,
    blockTypeId,
    blockState
);
sendToWorld(world, setBlock);
java
public class MyCustomPacket implements Packet {
    public static final int ID = 5000; // Custom ID (use high numbers)
    
    private final String message;
    private final int value;
    private final Vector3d position;
    
    // Deserialize constructor
    public MyCustomPacket(ByteBuf buffer) {
        this.message = PacketIO.readString(buffer);
        this.value = buffer.readInt();
        this.position = new Vector3d(
            buffer.readDouble(),
            buffer.readDouble(),
            buffer.readDouble()
        );
    }
    
    // Create constructor
    public MyCustomPacket(String message, int value, Vector3d position) {
        this.message = message;
        this.value = value;
        this.position = position;
    }
    
    @Override
    public int getId() {
        return ID;
    }
    
    @Override
    public void serialize(ByteBuf buffer) {
        PacketIO.writeString(buffer, message);
        buffer.writeInt(value);
        buffer.writeDouble(position.x());
        buffer.writeDouble(position.y());
        buffer.writeDouble(position.z());
    }
    
    @Override
    public int computeSize() {
        return PacketIO.stringSize(message) + 4 + 24; // int + 3 doubles
    }
    
    // Getters
    public String getMessage() { return message; }
    public int getValue() { return value; }
    public Vector3d getPosition() { return position; }
}

Creating Custom Packets

注册自定义数据包

Define Packet Class

java
public class MyCustomPacket implements Packet {
    public static final int ID = 5000; // Custom ID (use high numbers)
    
    private final String message;
    private final int value;
    private final Vector3d position;
    
    // Deserialize constructor
    public MyCustomPacket(ByteBuf buffer) {
        this.message = PacketIO.readString(buffer);
        this.value = buffer.readInt();
        this.position = new Vector3d(
            buffer.readDouble(),
            buffer.readDouble(),
            buffer.readDouble()
        );
    }
    
    // Create constructor
    public MyCustomPacket(String message, int value, Vector3d position) {
        this.message = message;
        this.value = value;
        this.position = position;
    }
    
    @Override
    public int getId() {
        return ID;
    }
    
    @Override
    public void serialize(ByteBuf buffer) {
        PacketIO.writeString(buffer, message);
        buffer.writeInt(value);
        buffer.writeDouble(position.x());
        buffer.writeDouble(position.y());
        buffer.writeDouble(position.z());
    }
    
    @Override
    public int computeSize() {
        return PacketIO.stringSize(message) + 4 + 24; // int + 3 doubles
    }
    
    // Getters
    public String getMessage() { return message; }
    public int getValue() { return value; }
    public Vector3d getPosition() { return position; }
}
java
@Override
protected void setup() {
    PacketRegistry.register(
        MyCustomPacket.ID,
        MyCustomPacket.class,
        MyCustomPacket::new,  // Deserializer
        MyCustomPacket::validate  // Optional validator
    );
}

// Optional validation
public static boolean validate(ByteBuf buffer) {
    // Quick validation without full parse
    return buffer.readableBytes() >= 28; // Minimum size
}

Register Custom Packet

PacketIO 工具类

写入数据

java
@Override
protected void setup() {
    PacketRegistry.register(
        MyCustomPacket.ID,
        MyCustomPacket.class,
        MyCustomPacket::new,  // Deserializer
        MyCustomPacket::validate  // Optional validator
    );
}

// Optional validation
public static boolean validate(ByteBuf buffer) {
    // Quick validation without full parse
    return buffer.readableBytes() >= 28; // Minimum size
}
java
// Primitives
buffer.writeByte(value);
buffer.writeShort(value);
buffer.writeInt(value);
buffer.writeLong(value);
buffer.writeFloat(value);
buffer.writeDouble(value);
buffer.writeBoolean(value);

// Strings
PacketIO.writeString(buffer, string);

// Variable-length integers
VarInt.write(buffer, value);

// Collections
PacketIO.writeArray(buffer, items, PacketIO::writeString);

// UUIDs
PacketIO.writeUUID(buffer, uuid);

// Vectors
PacketIO.writeVector3d(buffer, vector);
PacketIO.writeVector3i(buffer, blockPos);

// Optional values (nullable)
PacketIO.writeOptional(buffer, value, PacketIO::writeString);

PacketIO Utilities

读取数据

Writing Data

java
// Primitives
buffer.writeByte(value);
buffer.writeShort(value);
buffer.writeInt(value);
buffer.writeLong(value);
buffer.writeFloat(value);
buffer.writeDouble(value);
buffer.writeBoolean(value);

// Strings
PacketIO.writeString(buffer, string);

// Variable-length integers
VarInt.write(buffer, value);

// Collections
PacketIO.writeArray(buffer, items, PacketIO::writeString);

// UUIDs
PacketIO.writeUUID(buffer, uuid);

// Vectors
PacketIO.writeVector3d(buffer, vector);
PacketIO.writeVector3i(buffer, blockPos);

// Optional values (nullable)
PacketIO.writeOptional(buffer, value, PacketIO::writeString);
java
// Primitives
byte b = buffer.readByte();
short s = buffer.readShort();
int i = buffer.readInt();
long l = buffer.readLong();
float f = buffer.readFloat();
double d = buffer.readDouble();
boolean bool = buffer.readBoolean();

// Strings
String str = PacketIO.readString(buffer);

// Variable-length integers
int var = VarInt.read(buffer);

// Collections
List<String> items = PacketIO.readArray(buffer, PacketIO::readString);

// UUIDs
UUID uuid = PacketIO.readUUID(buffer);

// Vectors
Vector3d pos = PacketIO.readVector3d(buffer);
Vector3i blockPos = PacketIO.readVector3i(buffer);

// Optional values
String optional = PacketIO.readOptional(buffer, PacketIO::readString);

Reading Data

压缩处理

java
// Primitives
byte b = buffer.readByte();
short s = buffer.readShort();
int i = buffer.readInt();
long l = buffer.readLong();
float f = buffer.readFloat();
double d = buffer.readDouble();
boolean bool = buffer.readBoolean();

// Strings
String str = PacketIO.readString(buffer);

// Variable-length integers
int var = VarInt.read(buffer);

// Collections
List<String> items = PacketIO.readArray(buffer, PacketIO::readString);

// UUIDs
UUID uuid = PacketIO.readUUID(buffer);

// Vectors
Vector3d pos = PacketIO.readVector3d(buffer);
Vector3i blockPos = PacketIO.readVector3i(buffer);

// Optional values
String optional = PacketIO.readOptional(buffer, PacketIO::readString);
大型数据包会自动使用Zstd进行压缩:
java
public class LargeDataPacket implements Packet {
    public static final boolean IS_COMPRESSED = true;
    
    private final byte[] data;
    
    @Override
    public void serialize(ByteBuf buffer) {
        // Data will be automatically compressed if IS_COMPRESSED = true
        buffer.writeBytes(data);
    }
}
手动压缩:
java
// Compress
byte[] compressed = PacketIO.compress(data);

// Decompress
byte[] decompressed = PacketIO.decompress(compressed);

Compression

客户端定向自定义UI

自定义页面数据包

Large packets are automatically compressed using Zstd:
java
public class LargeDataPacket implements Packet {
    public static final boolean IS_COMPRESSED = true;
    
    private final byte[] data;
    
    @Override
    public void serialize(ByteBuf buffer) {
        // Data will be automatically compressed if IS_COMPRESSED = true
        buffer.writeBytes(data);
    }
}
Manual compression:
java
// Compress
byte[] compressed = PacketIO.compress(data);

// Decompress
byte[] decompressed = PacketIO.decompress(compressed);
向客户端发送自定义UI页面:
java
public void showCustomUI(Player player, String pageId, Map<String, Object> data) {
    CustomPage page = new CustomPage(
        pageId,
        serializeData(data)
    );
    player.getConnection().send(page);
}

Client-Bound Custom UI

窗口系统

Custom Page Packet

Send custom UI pages to clients:
java
public void showCustomUI(Player player, String pageId, Map<String, Object> data) {
    CustomPage page = new CustomPage(
        pageId,
        serializeData(data)
    );
    player.getConnection().send(page);
}
java
// Open custom window
OpenWindow openWindow = new OpenWindow(
    windowId,
    "MyPlugin:CustomWindow",
    windowData
);
player.getConnection().send(openWindow);

// Handle window actions
handler.registerHandler(SendWindowAction.ID, packet -> {
    SendWindowAction action = (SendWindowAction) packet;
    int windowId = action.getWindowId();
    String actionType = action.getActionType();
    
    processWindowAction(player, windowId, actionType, action.getData());
});

// Close window
CloseWindow closeWindow = new CloseWindow(windowId);
player.getConnection().send(closeWindow);

Window System

处理客户端输入

鼠标交互

java
// Open custom window
OpenWindow openWindow = new OpenWindow(
    windowId,
    "MyPlugin:CustomWindow",
    windowData
);
player.getConnection().send(openWindow);

// Handle window actions
handler.registerHandler(SendWindowAction.ID, packet -> {
    SendWindowAction action = (SendWindowAction) packet;
    int windowId = action.getWindowId();
    String actionType = action.getActionType();
    
    processWindowAction(player, windowId, actionType, action.getData());
});

// Close window
CloseWindow closeWindow = new CloseWindow(windowId);
player.getConnection().send(closeWindow);
java
handler.registerHandler(MouseInteraction.ID, packet -> {
    MouseInteraction interaction = (MouseInteraction) packet;
    
    MouseButton button = interaction.getButton();
    Vector3d hitPos = interaction.getHitPosition();
    Vector3i blockPos = interaction.getBlockPosition();
    int entityId = interaction.getEntityId();
    
    if (button == MouseButton.RIGHT) {
        handleRightClick(player, hitPos, blockPos, entityId);
    } else if (button == MouseButton.LEFT) {
        handleLeftClick(player, hitPos, blockPos, entityId);
    }
});

Handling Client Input

移动操作

Mouse Interaction

java
handler.registerHandler(MouseInteraction.ID, packet -> {
    MouseInteraction interaction = (MouseInteraction) packet;
    
    MouseButton button = interaction.getButton();
    Vector3d hitPos = interaction.getHitPosition();
    Vector3i blockPos = interaction.getBlockPosition();
    int entityId = interaction.getEntityId();
    
    if (button == MouseButton.RIGHT) {
        handleRightClick(player, hitPos, blockPos, entityId);
    } else if (button == MouseButton.LEFT) {
        handleLeftClick(player, hitPos, blockPos, entityId);
    }
});
java
handler.registerHandler(ClientMovement.ID, packet -> {
    ClientMovement movement = (ClientMovement) packet;
    
    Vector3d position = movement.getPosition();
    Vector3f rotation = movement.getRotation();
    Vector3d velocity = movement.getVelocity();
    boolean onGround = movement.isOnGround();
    
    validateMovement(player, position, velocity);
});

Movement

键盘输入

java
handler.registerHandler(ClientMovement.ID, packet -> {
    ClientMovement movement = (ClientMovement) packet;
    
    Vector3d position = movement.getPosition();
    Vector3f rotation = movement.getRotation();
    Vector3d velocity = movement.getVelocity();
    boolean onGround = movement.isOnGround();
    
    validateMovement(player, position, velocity);
});
java
handler.registerHandler(KeyInputPacket.ID, packet -> {
    KeyInputPacket input = (KeyInputPacket) packet;
    
    int keyCode = input.getKeyCode();
    boolean pressed = input.isPressed();
    
    handleKeyInput(player, keyCode, pressed);
});

Key Input

请求频率限制

java
handler.registerHandler(KeyInputPacket.ID, packet -> {
    KeyInputPacket input = (KeyInputPacket) packet;
    
    int keyCode = input.getKeyCode();
    boolean pressed = input.isPressed();
    
    handleKeyInput(player, keyCode, pressed);
});
防止数据包滥用:
java
public class RateLimitedHandler implements SubPacketHandler {
    private final Map<UUID, RateLimiter> limiters = new ConcurrentHashMap<>();
    
    @Override
    public void registerHandlers(IPacketHandler handler) {
        handler.registerHandler(MyPacket.ID, this::handleWithRateLimit);
    }
    
    private void handleWithRateLimit(Packet packet) {
        Player player = getPacketSender();
        RateLimiter limiter = limiters.computeIfAbsent(
            player.getUUID(), 
            k -> new RateLimiter(10, 1000) // 10 per second
        );
        
        if (!limiter.tryAcquire()) {
            getLogger().atWarning().log("Rate limit exceeded for %s", player.getName());
            return;
        }
        
        processPacket(packet);
    }
}

Rate Limiting

错误处理

Protect against packet spam:
java
public class RateLimitedHandler implements SubPacketHandler {
    private final Map<UUID, RateLimiter> limiters = new ConcurrentHashMap<>();
    
    @Override
    public void registerHandlers(IPacketHandler handler) {
        handler.registerHandler(MyPacket.ID, this::handleWithRateLimit);
    }
    
    private void handleWithRateLimit(Packet packet) {
        Player player = getPacketSender();
        RateLimiter limiter = limiters.computeIfAbsent(
            player.getUUID(), 
            k -> new RateLimiter(10, 1000) // 10 per second
        );
        
        if (!limiter.tryAcquire()) {
            getLogger().atWarning().log("Rate limit exceeded for %s", player.getName());
            return;
        }
        
        processPacket(packet);
    }
}
java
handler.registerHandler(MyPacket.ID, packet -> {
    try {
        processPacket(packet);
    } catch (Exception e) {
        getLogger().atSevere().withCause(e).log("Error processing packet");
        
        // Optionally disconnect on critical errors
        if (e instanceof CriticalPacketError) {
            player.disconnect("Protocol error");
        }
    }
});

Error Handling

数据包验证

java
handler.registerHandler(MyPacket.ID, packet -> {
    try {
        processPacket(packet);
    } catch (Exception e) {
        getLogger().atSevere().withCause(e).log("Error processing packet");
        
        // Optionally disconnect on critical errors
        if (e instanceof CriticalPacketError) {
            player.disconnect("Protocol error");
        }
    }
});
java
public class ValidatedPacket implements Packet {
    @Override
    public void serialize(ByteBuf buffer) {
        // Add checksum
        int checksum = computeChecksum();
        buffer.writeInt(checksum);
        // Write data
    }
    
    public static ValidatedPacket deserialize(ByteBuf buffer) {
        int checksum = buffer.readInt();
        // Read data
        ValidatedPacket packet = new ValidatedPacket(...);
        
        if (packet.computeChecksum() != checksum) {
            throw new PacketValidationException("Checksum mismatch");
        }
        
        return packet;
    }
}

Packet Validation

性能优化技巧

数据包批处理

java
public class ValidatedPacket implements Packet {
    @Override
    public void serialize(ByteBuf buffer) {
        // Add checksum
        int checksum = computeChecksum();
        buffer.writeInt(checksum);
        // Write data
    }
    
    public static ValidatedPacket deserialize(ByteBuf buffer) {
        int checksum = buffer.readInt();
        // Read data
        ValidatedPacket packet = new ValidatedPacket(...);
        
        if (packet.computeChecksum() != checksum) {
            throw new PacketValidationException("Checksum mismatch");
        }
        
        return packet;
    }
}
java
public class PacketBatcher {
    private final List<Packet> pending = new ArrayList<>();
    private final Player player;
    
    public void queue(Packet packet) {
        pending.add(packet);
    }
    
    public void flush() {
        if (pending.isEmpty()) return;
        
        // Send as batch if supported
        BatchPacket batch = new BatchPacket(pending);
        player.getConnection().send(batch);
        pending.clear();
    }
}

Performance Tips

增量压缩

Packet Batching

java
public class PacketBatcher {
    private final List<Packet> pending = new ArrayList<>();
    private final Player player;
    
    public void queue(Packet packet) {
        pending.add(packet);
    }
    
    public void flush() {
        if (pending.isEmpty()) return;
        
        // Send as batch if supported
        BatchPacket batch = new BatchPacket(pending);
        player.getConnection().send(batch);
        pending.clear();
    }
}
仅发送变更内容:
java
public class EntityStateSync {
    private final Map<Integer, EntityState> lastSent = new HashMap<>();
    
    public void sync(Player player, List<Entity> entities) {
        List<EntityDelta> deltas = new ArrayList<>();
        
        for (Entity entity : entities) {
            EntityState current = captureState(entity);
            EntityState last = lastSent.get(entity.getId());
            
            if (last == null || !current.equals(last)) {
                deltas.add(computeDelta(last, current));
                lastSent.put(entity.getId(), current);
            }
        }
        
        if (!deltas.isEmpty()) {
            player.getConnection().send(new EntityDeltaPacket(deltas));
        }
    }
}

Delta Compression

延迟序列化

Only send changes:
java
public class EntityStateSync {
    private final Map<Integer, EntityState> lastSent = new HashMap<>();
    
    public void sync(Player player, List<Entity> entities) {
        List<EntityDelta> deltas = new ArrayList<>();
        
        for (Entity entity : entities) {
            EntityState current = captureState(entity);
            EntityState last = lastSent.get(entity.getId());
            
            if (last == null || !current.equals(last)) {
                deltas.add(computeDelta(last, current));
                lastSent.put(entity.getId(), current);
            }
        }
        
        if (!deltas.isEmpty()) {
            player.getConnection().send(new EntityDeltaPacket(deltas));
        }
    }
}
java
public class LazyPacket implements Packet {
    private ByteBuf cached;
    
    @Override
    public void serialize(ByteBuf buffer) {
        if (cached == null) {
            cached = Unpooled.buffer();
            doSerialize(cached);
        }
        buffer.writeBytes(cached.duplicate());
    }
}

Lazy Serialization

故障排查

数据包未被接收

java
public class LazyPacket implements Packet {
    private ByteBuf cached;
    
    @Override
    public void serialize(ByteBuf buffer) {
        if (cached == null) {
            cached = Unpooled.buffer();
            doSerialize(cached);
        }
        buffer.writeBytes(cached.duplicate());
    }
}
  1. 验证数据包ID已注册
  2. 检查处理器已注册
  3. 确保数据包已正确序列化
  4. 检查处理器中是否存在异常

Troubleshooting

反序列化错误

Packet Not Received

  1. Verify packet ID is registered
  2. Check handler is registered
  3. Ensure packet is properly serialized
  4. Check for exceptions in handler
  1. 验证读取顺序与写入顺序一致
  2. 检查数据类型大小
  3. 验证缓冲区有足够字节
  4. 添加边界检查

Deserialization Errors

连接断开

  1. Verify read order matches write order
  2. Check data type sizes
  3. Validate buffer has enough bytes
  4. Add bounds checking
  1. 检查是否存在未处理的异常
  2. 验证数据包大小限制
  3. 监控带宽使用情况
  4. 检查 ping/超时设置
详见
references/packet-list.md
获取完整数据包目录。 详见
references/serialization.md
获取序列化模式说明。

Connection Drops

  1. Check for unhandled exceptions
  2. Verify packet size limits
  3. Monitor bandwidth usage
  4. Check ping/timeout settings
See
references/packet-list.md
for complete packet catalog. See
references/serialization.md
for serialization patterns.