网络与同步 API

OpenModsLib 提供 4 条独立的 FML 网络通道,覆盖数据存储 ID 同步、远程过程调用、状态同步与网络事件广播。这是它作为库的第二层对外契约(第一层是 openmods.api 的接口混入,见公开 API 接口)。

基本信息

属性 值
通道数 4
通道名前缀 OpenMods|
network 包类数 41(根 5 + rpc 13 + event 11 + senders 6 + targets 3 + rpc/targets 3)
sync 包类数 34(含 drops 子包)
通道注册时机 SyncChannelHolder.ensureLoaded() 在 preInit 首条语句(src/main/java/openmods/OpenMods.java:72);其余在各自实例首次访问时
崩溃报告条目 "Stencil buffer state"(客户端,渲染相关,与通道无关)

功能

4 条通道

通道名 常量 管理类 用途 声明行 注册行
OpenMods|I IdSyncManager.CHANNEL_NAME openmods.network.IdSyncManager 数据存储 ID 在客户端/服务端间的同步 src/main/java/openmods/network/IdSyncManager.java:48 :111
OpenMods|RPC RpcCallDispatcher.CHANNEL_NAME openmods.network.rpc.RpcCallDispatcher 远程过程调用(动态代理) src/main/java/openmods/network/rpc/RpcCallDispatcher.java:20 :35-38
OpenMods|M SyncChannelHolder.CHANNEL_NAME openmods.sync.SyncChannelHolder TileEntity / 实体状态增量同步 src/main/java/openmods/sync/SyncChannelHolder.java:23 :30-32
OpenMods|E NetworkEventDispatcher.CHANNEL_NAME openmods.network.event.NetworkEventDispatcher 自定义网络事件广播 src/main/java/openmods/network/event/NetworkEventDispatcher.java:13 :20-22

四条通道全部经 NetworkRegistry.INSTANCE.newChannel(...) 注册(4 处调用点已交叉核对)。包在 FMLProxyPacket 中作为通道标识(IdSyncManager.java:107、SyncChannelHolder.java:40,44),ExtendedOutboundHandler.install 统一安装出站处理器(RpcCallDispatcher.java:39、SyncChannelHolder.java:35、ExtendedOutboundHandler.java:75-79)并按 NetworkRegistry.CHANNEL_SOURCE 判断发送侧(ExtendedOutboundHandler.java:49)。

ensureLoaded() 的空方法陷阱

SyncChannelHolder.ensureLoaded()(src/main/java/openmods/sync/SyncChannelHolder.java:49)的方法体是空的:

public static void ensureLoaded() {}

它不执行任何显式逻辑,通道注册完全发生在类初始化的副作用中:调用静态方法会触发 <clinit> → 初始化 INSTANCE(:25)→ 执行私有构造器(:29-38)→ 在其中调用 newChannel 并为每个 Side 建立发送器。OpenMods.preInit 把这条空调用放在第一条语句(OpenMods.java:72),正是为了保证通道在任何其它 mod 的 preInit 之前就绪。

阅读源码时若只看方法体会误判其为无效代码 —— 实际效果由 JVM 类初始化语义保证,不能据此认为该通道未注册。

RPC —— 远程过程调用

RpcCallDispatcher(src/main/java/openmods/network/rpc/RpcCallDispatcher.java:17)是单例(:19 INSTANCE),提供动态代理式 RPC:

注册阶段(必须在 LoaderState.PREINITIALIZATION 内,OpenMods.java:76-81):

RpcCallDispatcher.INSTANCE.startRegistration()
    .registerInterface(IRpcDirectionBitMap.class)
    .registerInterface(IRpcIntBitMap.class)
    .registerTargetWrapper(EntityRpcTarget.class)
    .registerTargetWrapper(TileEntityRpcTarget.class)
    .registerTargetWrapper(SyncRpcTarget.SyncEntityRpcTarget.class)
    .registerTargetWrapper(SyncRpcTarget.SyncTileEntityRpcTarget.class);

startRegistration(RpcCallDispatcher.java:49)带 Preconditions.checkState(Loader.instance().isInState(LoaderState.PREINITIALIZATION)) 守卫(:50-53)—— 在其它加载阶段调用会直接抛异常。注册 2 个接口(RpcSetup.registerInterface,src/main/java/openmods/network/rpc/RpcSetup.java:44)+ 4 个 target wrapper(RpcSetup.java:73)。postInit 阶段由 finishRegistration()(OpenMods.java:124)收尾并把 setup 置空(RpcCallDispatcher.java:56-61),之后不可再注册。

使用:createProxy(wrapper, sender, mainIntf, extraIntf...)(RpcCallDispatcher.java:61)为任意接口生成客户端代理,调用时自动编码为网络包发往服务端。方法与目标实体的对应关系由 MethodIdRegistry 与 TargetWrapperRegistry 维护。

RpcSetup.ID_FIELDS_SEPARATOR = ";"(RpcSetup.java:19)是 RPC 方法签名(声明类名 + 字段名 + 参数类型)的拼接分隔符(:60-62)。

状态同步 —— sync 包

SyncChannelHolder(src/main/java/openmods/sync/SyncChannelHolder.java)为客户端与服务端各建一条通道(:30-32),承载 ISyncableObject(src/main/java/openmods/sync/ISyncableObject.java:11)的增量同步:

方法 用途
isDirty() / markDirty() / markClean() 脏标记三件套
writeToStream / readFromStream 网络增量传输
writeToNBT / readFromNBT 存档持久化

提供 20 个 Syncable* 实现(src/main/java/openmods/sync/ 目录下),覆盖常用数据类型:

类别 实现
基础数值 SyncableBoolean SyncableByte SyncableShort SyncableInt SyncableFloat SyncableDouble SyncableVarInt SyncableUnsignedByte
数组 SyncableByteArray SyncableIntArray
字符串与标识 SyncableString SyncableUUID SyncableEnum
游戏对象 SyncableBlock SyncableItemStack SyncableNBT SyncableTank
复合 SyncableFlags SyncableSides SyncableObjectBase(基类)

配套的高层封装:SyncableBlock(方块状态)、SyncMap / SyncMapEntity / SyncMapTile(Map 结构同步)、SyncedTileEntity / SyncableObjectBase(TileEntity 集成)、SyncObjectScanner(自动扫描)。drops 子包专用于掉落物同步。

ID 同步 —— IdSyncManager

IdSyncManager(src/main/java/openmods/network/IdSyncManager.java:46)继承 DataStoreManager,解决的是「服务端为数据存储分配的 ID 如何告知客户端」这一问题。提供 createDataStore 两个重载(:114,121),返回 DataStoreBuilder。在 FMLNetworkEvent.ClientConnectedToServerEvent 时向客户端同步(:76-80),并在 ClientDisconnectionFromServerEvent 时清理(onDisconnect,:157)。finishLoading()(:162)在 postInit 末尾调用(OpenMods.java:127,源码注释 // must be after all builders are done)。

网络事件 —— NetworkEventDispatcher

NetworkEventDispatcher(src/main/java/openmods/network/event/NetworkEventDispatcher.java)提供 NetworkEventCodec(编解码)+ NetworkEventInboundHandler(入站)两个处理器,注册于通道 OpenMods|E(:20-22)。NetworkEventCodec 会把事件对象回填 sender 字段(:110),使接收端能知道事件来自哪个玩家。

数值

数值名 值
网络通道数 4(OpenMods|I / |RPC / |M / |E)
newChannel 注册调用点 4
Syncable* 实现类数 20(含基类 SyncableObjectBase)
ISyncableObject 方法数 6
RPC 注册的接口数 2(IRpcDirectionBitMap、IRpcIntBitMap)
RPC 注册的 target wrapper 数 4
network 包类数 41
sync 包类数 34

交互

4 条通道均无玩家可见交互。它们只在下层被 OpenBlocks 等下游 mod 调用。

唯一与网络相关的玩家侧提示是崩溃报告条目 "Stencil buffer state"(src/main/java/openmods/proxy/OpenClientProxy.java:113),它输出当前 OpenGL 方法集与模板缓冲池状态(StencilPoolManager),与上述 4 条通道无关,属于渲染诊断。

错误处理

  • ExtendedOutboundHandler(src/main/java/openmods/network/ExtendedOutboundHandler.java)在发送失败时 catch (Throwable) 记录日志(:61)
  • IdSyncManager(:58)与 NetworkEventCodec(:52,84)在通道侧别不匹配时走异常分支
  • RpcCallDispatcher.startRegistration 用 Preconditions.checkState 硬失败,阻止在错误加载阶段注册
  • SyncChannelHolder.ensureLoaded() 被 OpenMods.preInit 的第一条语句调用(OpenMods.java:72),通过类初始化保证其它 mod 在 preInit 中使用时通道已就绪

相关条目