UE5.3 把 Iris 作为实验性功能放进引擎(Engine/Plugins/Experimental/Iris),目标不是修修补补,而是重写整套复制系统。从 5.3 到 5.5 它一直在实验状态,但架构思路已经很清晰,而且不少设计会回流影响老系统。基于 5.3 源码(Iris/Core、Iris/Replication)梳理它和传统模型的差异。
老系统的结构性痛点
传统复制系统的几个痛是结构性的,不是优化能解决的:
- 对象模型绑定 UObject。
UActorChannel直接持AActor*裸指针,影子状态按对象内存布局拷贝。这导致:非 actor 对象(比如一个纯数据的子结构)想复制就得包成 UObject;channel 的生命周期和 actor 的 GC 纠缠在一起。 - 协议和对象布局耦合。bunch 里的属性 handle 是按
UClass的FProperty顺序编号的——类布局一变(热更、版本差异),协议就断。补丁兼容只能做 Specialize,脆弱。 - 相关性计算和状态序列化在一个 tick 里耦合。ReplicationGraph 用缓存缓解了这个痛,但缓存和协议层是两套东西,维护两份一致性。
Iris 对这三个痛各给了一个结构性解法。
解法一:NetRefHandle 解耦对象模型
Iris 引入 NetRefHandle(Iris/Core/NetRefHandle.h)——一个 32 位句柄,替代老系统的 UObject* + channel 组合。对象想参与复制,先向 Iris 注册拿到 handle,之后协议层只认 handle,不再碰 UObject。
这个间接层的意义:
- 协议层不再依赖 UObject/GC。对象销毁时 Iris 做 detach,handle 失效,客户端收到"handle 失效"通知清理本地镜像——没有 channel close 和 actor 销毁的竞争窗口(老系统里
ConditionalBeginDestroy和 channel 关闭的时序 bug 不少)。 - 非 actor 对象可以平等参与复制。一个
USTRUCT注册成 replicated 对象照样拿 handle,不用伪造 actor。
代价是多一层间接:UObject ↔ handle 的映射表要维护,调试时从 handle 反查对象需要走 NetRefHandleManager。
解法二:Replication Fragment 拆状态
传统模型里"一个对象哪些属性要复制"由 UClass 的反射信息整体描述,粒度是类。Iris 拆成复制片段(Replication Fragment):每个对象可以提供多个 fragment,每个 fragment 描述一组内聚的状态(比如"移动状态"、"战斗属性"、"外观"),fragment 是协议和序列化的基本单元。
好处有三个:
- 协议按 fragment 组织,fragment 有自己的 schema 和序列化器,类布局变化只要 fragment 的 schema 没变,协议就兼容——补丁场景的脆弱性大幅改善。
- 相关性可以按 fragment 做。移动 fragment 高频同步、外观 fragment 低频,同一对象的不同状态走不同 cadence,不用拆成多个 actor。
- 序列化器可插拔。fragment 的序列化不绑定
FProperty遍历,可以注册自定义 serializer(比如对移动状态做专门的 delta 压缩)。
解法三:Tokenized Protocol + 状态集中管理
Iris 的线协议叫 Tokenized Protocol(Iris/Replication/Protocol):bunch 流被拆成 token 序列(对象创建、fragment 更新、handle 失效……),每个 token 独立解析,不再依赖类布局推导。配套的是服务端状态存储:老系统的发送历史是每 channel 一份影子快照(环形缓冲,属性同步那篇讲过);Iris 把"对象的当前权威状态"集中存放在 ReplicationStorage,delta 计算从存储里直接取,不再每连接存一份影子。
账要这么算:老系统 $N_{\text{conn}}$ 个连接 × 每连接一份影子状态的内存,Iris 收敛为一份集中存储 + 每连接的 ack 游标。100 连接的场景,服务端网络内存的 actor 状态部分降一个量级。
现状判断:什么时候碰它
5.3 的 Iris 是实验性的,明确不建议生产使用——GAS 等大量引擎子系统还没适配,开 Iris 跑 GAS 项目会直接缺功能。但两点值得现在做:
- 新项目预留接口。如果你的项目要长期维护(三年以上的在线游戏),自定义复制逻辑封装在一层接口后面,将来 Iris 转正时迁移成本可控。
- 学它的设计决策。NetRefHandle 的间接层、fragment 的拆状态、集中存储收敛内存——这些思路不依赖 Iris 本身:比如你自己写 replay 系统时,"状态集中存储 + 每连接游标"就是可以直接抄的架构。
判断 Iris 是否可用了,看三件事:GAS/CharacterMovement 是否完成适配、net.Iris 相关的控制台命令是否稳定、Epic 的官方 release note 是否摘掉 experimental 标记。在那之前,传统系统 + ReplicationGraph 仍是生产环境的正解。
Iris 和老系统的关系可以类比 Chaos 取代 PhysX:不是增量优化,是换地基。换地基期间老系统继续维护,新系统的架构决策逐步回流。对引擎使用者的启示是:复制系统的接口抽象(比如你自己封装的网络同步层)值得按"未来会被换掉"来设计。