🎯 Iris 网络复制系统技术分析 - 第四部分:核心组件详解

如果说 Iris 是一个高效的物流公司,那么本章介绍的五大核心组件就是这家公司的关键部门。让我们一起认识这些"部门经理"们!
📋 组件关系总览
在深入每个组件之前,先来看看它们之间的关系:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🎮 游戏世界 │
│ (AActor, UActorComponent) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 🌉 UEngineReplicationBridge │
│ "UE引擎的翻译官" │
│ 负责将 Actor/Component 转换为 Iris 能理解的格式 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 🔗 UObjectReplicationBridge │
│ "对象管理专家" │
│ 处理休眠、轮询频率、依赖关系等通用对象逻辑 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 🌐 UReplicationBridge │
│ "桥接基础设施" │
│ 提供网络对象创建、销毁、关卡组管理等基础能力 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 🧠 UReplicationSystem │
│ "总指挥中心" │
│ 协调所有子系统:过滤、优先级、连接管理、数据发送 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 🎫 FNetRefHandleManager │
│ "身份证管理局" │
│ 为每个网络对象分配唯一ID,管理对象索引 │
└─────────────────────────────────────────────────────────────────────┘🏠 生活化类比
想象你经营一家国际快递公司:
组件 | 类比角色 | 职责 |
|---|---|---|
| 📊 总经理办公室 | 制定策略、协调各部门、做最终决策 |
| 🌉 物流中转站 | 接收包裹、分拣、打包、发送 |
| 📦 包裹处理中心 | 决定包裹优先级、是否暂存、多久检查一次 |
| 🏭 工厂对接部 | 与生产线(游戏引擎)对接,收取原材料(Actor) |
| 🎫 快递单号系统 | 给每个包裹分配唯一追踪号 |
🧠 4.1 UReplicationSystem - 总指挥中心
💡 它是什么?
UReplicationSystem 是 Iris 的大脑,负责协调整个网络复制系统的运作。就像一个交响乐团的指挥,它不亲自演奏任何乐器,但确保所有乐手在正确的时间演奏正确的音符。
PLAINTEXT
┌────────────────────────────────────────────────────────────────┐
│ 🧠 UReplicationSystem │
│ "总指挥中心" │
├────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 🔗 连接管理 │ │ 🎯 过滤系统 │ │ ⚡ 优先级系统 │ │
│ │ Connections │ │ Filtering │ │Prioritization│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 📁 组管理 │ │ 📡 RPC处理 │ │ 📊 脏数据追踪 │ │
│ │ Groups │ │ RPC │ │ DirtyTracker │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 🗜️ 增量压缩 │ │ 📋 条件复制 │ │ 🎫 句柄管理 │ │
│ │ Delta │ │ Conditionals │ │ HandleMgr │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────┘🏗️ 内部架构:FReplicationSystemImpl
UReplicationSystem 采用 Pimpl(Pointer to Implementation) 设计模式,真正的"大脑"是 FReplicationSystemImpl,它管理着所有子系统。
📖 源码位置: ReplicationSystem.cpp:55-91
CPP
// ReplicationSystem.cpp:55-91 - FReplicationSystemImpl 核心成员class FReplicationSystemImpl
{
public:
// 附件发送策略映射
TMap<FObjectKey, ENetObjectAttachmentSendPolicyFlags> AttachmentSendPolicyFlags;
// 指向公开接口的指针
UReplicationSystem* ReplicationSystem;
// Token 存储(用于名称/字符串复制)
FNetTokenStore* NetTokenStore;
// 核心内部系统 - 包含所有子系统
FReplicationSystemInternal ReplicationSystemInternal;
// 特殊过滤组句柄
FNetObjectGroupHandle NotReplicatedNetObjectGroupHandle; // 不复制组
FNetObjectGroupHandle NetGroupOwnerNetObjectGroupHandle; // 所有者组
FNetObjectGroupHandle NetGroupReplayNetObjectGroupHandle; // 回放组
// 待发送连接的位数组
FNetBitArray ConnectionsPendingPostTickDispatchSend;
// 当前发送阶段
EReplicationSystemSendPass CurrentSendPass = EReplicationSystemSendPass::Invalid;
};🔧 初始化流程深度剖析
当创建 UReplicationSystem 时,会触发一系列精密的初始化操作。
📖 源码位置: ReplicationSystem.cpp:106-293
CPP
// ReplicationSystem.cpp:106-293 - Init 方法核心流程void FReplicationSystemImpl::Init(const UReplicationSystem::FReplicationSystemParams& Params){
// 1️⃣ 验证 NetTokenStore(必须有效)
bool bHasValidNetTokenStore = ensureAlwaysMsgf(Params.NetTokenStore,
TEXT("ReplicationSystem cannot be initialized without a valid NetTokenStore"));
// 2️⃣ 初始化 NetRefHandleManager - 对象句柄管理器
FNetRefHandleManager& NetRefHandleManager = ReplicationSystemInternal.GetNetRefHandleManager();
{
FNetRefHandleManager::FInitParams NetRefHandleManagerInitParams;
NetRefHandleManagerInitParams.ReplicationSystemId = ReplicationSystemId;
NetRefHandleManagerInitParams.MaxActiveObjectCount = Params.MaxReplicatedObjectCount;
NetRefHandleManagerInitParams.InternalNetRefIndexInitSize = Params.InitialNetObjectListCount;
NetRefHandleManagerInitParams.InternalNetRefIndexGrowSize = Params.NetObjectListGrowCount;
NetRefHandleManager.Init(NetRefHandleManagerInitParams);
// 订阅索引增长事件 - 当对象数量超过当前容量时触发
NetRefHandleManager.GetOnMaxInternalNetRefIndexIncreasedDelegate()
.AddRaw(this, &FReplicationSystemImpl::OnMaxInternalNetRefIndexIncreased);
}
// 3️⃣ 初始化脏对象追踪器(仅服务器需要)
if (Params.bAllowObjectReplication)
{
FDirtyNetObjectTrackerInitParams DirtyNetObjectTrackerInitParams;
DirtyNetObjectTrackerInitParams.NetRefHandleManager = &NetRefHandleManager;
DirtyNetObjectTrackerInitParams.MaxInternalNetRefIndex = CurrentMaxInternalNetRefIndex;
ReplicationSystemInternal.InitDirtyNetObjectTracker(DirtyNetObjectTrackerInitParams);
}
// 4️⃣ 初始化状态存储系统
{
FReplicationStateStorage& StateStorage = ReplicationSystemInternal.GetReplicationStateStorage();
FReplicationStateStorageInitParams InitParams;
InitParams.MaxObjectCount = AbsoluteMaxObjectCount;
InitParams.MaxDeltaCompressedObjectCount = Params.MaxDeltaCompressedObjectCount;
StateStorage.Init(InitParams);
}
// 5️⃣ 初始化对象组系统
{
FNetObjectGroups& Groups = ReplicationSystemInternal.GetGroups();
FNetObjectGroupInitParams InitParams;
InitParams.MaxGroupCount = Params.MaxNetObjectGroupCount;
Groups.Init(InitParams);
}
// 6️⃣ 初始化过滤系统
{
FReplicationFiltering& ReplicationFiltering = ReplicationSystemInternal.GetFiltering();
FReplicationFilteringInitParams InitParams;
InitParams.ReplicationSystem = ReplicationSystem;
InitParams.MaxGroupCount = Params.MaxNetObjectGroupCount;
ReplicationFiltering.Init(InitParams);
}
// 7️⃣ 初始化默认过滤组
InitDefaultFilteringGroups();
// 8️⃣ 初始化条件复制系统 & 优先级系统
ReplicationSystemInternal.GetConditionals().Init(InitParams);
ReplicationSystemInternal.GetPrioritization().Init(InitParams);
// 9️⃣ 初始化 ReplicationBridge
ReplicationSystemInternal.SetReplicationBridge(Params.ReplicationBridge);
ReplicationSystemInternal.GetReplicationBridge()->Initialize(ReplicationSystem);
// 🔟 初始化 NetBlobManager(处理 RPC 和附件)
{
FNetBlobManager& BlobManager = ReplicationSystemInternal.GetNetBlobManager();
FNetBlobManagerInitParams InitParams;
InitParams.bSendAttachmentsWithObject = ReplicationSystem->IsServer();
BlobManager.Init(InitParams);
}
}⚡ 主更新循环 (NetUpdate)
这是 Iris 的心跳!每一帧,服务器都会执行这个更新循环,决定哪些数据需要发送给哪些客户端。
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🔄 NetUpdate 主循环 │
│ "每帧的工作流程" │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ 🚀 PreSendUpdate │ ◄── 发送前准备(最复杂的阶段)
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1️⃣ 更新脏对象列表 │
│ "检查哪些包裹内容变了" │
│ │
│ 2️⃣ 更新世界位置 │
│ "更新所有包裹的当前位置" │
│ │
│ 3️⃣ 更新过滤 │
│ "决定哪些包裹该发给哪些客户" │
│ │
│ 4️⃣ 更新条件 │
│ "检查特殊发送条件" │
│ │
│ 5️⃣ 量化脏状态数据 │
│ "把数据压缩成网络友好格式" │
│ │
│ 6️⃣ 处理附件发送队列 │
│ "处理大文件分片发送" │
│ │
│ 7️⃣ 更新对象作用域 │
│ "确定每个连接能看到哪些对象" │
│ │
│ 8️⃣ 传播脏变更 │
│ "标记需要发送的数据" │
│ │
│ 9️⃣ 更新优先级 │
│ "决定发送顺序" │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ 📤 SendUpdate │ ◄── 实际发送数据
└────────┬────────┘
│
▼
┌─────────────────┐
│ 🧹 PostSendUpdate│ ◄── 发送后清理
└─────────────────┘🔍 PreSendUpdate 详解
CPP
void UReplicationSystem::PreSendUpdate(const FSendUpdateParams& Params){
// 🏁 启动预发送更新
Impl->StartPreSendUpdate();
// 1️⃣ 更新脏对象列表 - "哪些数据变化了?"
Impl->UpdateDirtyObjectList();
// 2️⃣ 更新世界位置 - "对象现在在哪里?"
Impl->UpdateWorldLocations();
// 3️⃣ 更新过滤 - "谁应该看到什么?"
Impl->UpdateFiltering();
// 4️⃣ 调用预发送回调
Impl->CallPreSendUpdate(Params.DeltaSeconds);
// 5️⃣ 完成脏对象列表
Impl->UpdateDirtyListPostPoll();
// 6️⃣ 更新条件 - "特殊条件检查"
Impl->UpdateConditionals();
// 7️⃣ 量化脏状态数据 - "压缩数据"
Impl->QuantizeDirtyStateData();
// 8️⃣ 处理附件发送队列
Impl->ProcessNetObjectAttachmentSendQueue(...);
// 9️⃣ 更新对象作用域
Impl->UpdateObjectScopes();
// 🔟 传播脏变更
Impl->PropagateDirtyChanges();
// 1️⃣1️⃣ 更新优先级
Impl->UpdatePrioritization(ReplicatingConnections);
}📖 源码位置: Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationSystem.cpp:738-838
🎬 生活化比喻:快递公司的一天
想象你是快递公司的调度员,每天早上(每帧)你需要:
检查包裹变化 📦 - 看看哪些包裹内容更新了
更新位置信息 📍 - 确认所有包裹现在在哪个仓库
分配配送区域 🗺️ - 决定哪个快递员负责哪个区域
检查特殊要求 ⚠️ - 有些包裹只能本人签收
打包压缩 📦 - 把包裹打包得更紧凑
处理超大件 🚛 - 大包裹需要分批发送
确定可见范围 👀 - 每个客户能查看哪些包裹状态
标记待发送 ✅ - 确定今天要发的包裹
排定优先级 🔢 - 急件先发,普通件后发
🔌 连接管理接口
管理客户端连接,就像管理快递公司的各个配送站点:
CPP
// 📥 添加新连接 - "新开了一个配送站"void AddConnection(uint32 ConnectionId);
// 📤 移除连接 - "关闭一个配送站"void RemoveConnection(uint32 ConnectionId);
// ✅ 验证连接是否有效bool IsValidConnection(uint32 ConnectionId) const;
// 👋 优雅关闭连接 - "通知配送站准备关门"void SetConnectionGracefullyClosing(uint32 ConnectionId);
// 🔛 启用/禁用连接的复制功能void SetReplicationEnabledForConnection(uint32 ConnectionId, bool bEnabled);📦 对象管理接口
管理需要复制的游戏对象:
CPP
// ═══════════════════════════════════════════════════════════════════// 📁 组管理// ═══════════════════════════════════════════════════════════════════
// 创建一个新组 - 就像创建一个"分类标签"FNetObjectGroupHandle CreateGroup(FName GroupName);
// 销毁组void DestroyGroup(FNetObjectGroupHandle GroupHandle);
// 将对象添加到组 - "给包裹贴标签"void AddToGroup(FNetObjectGroupHandle GroupHandle, FNetRefHandle Handle);
// 从组中移除对象void RemoveFromGroup(FNetObjectGroupHandle GroupHandle, FNetRefHandle Handle);
// ═══════════════════════════════════════════════════════════════════// 🎯 过滤设置// ═══════════════════════════════════════════════════════════════════
// 设置对象的过滤器 - "决定谁能收到这个包裹"bool SetFilter(FNetRefHandle Handle,
FNetObjectFilterHandle FilterHandle,
FName FilterConfigProfile);
// 设置对象的所有者连接 - "指定包裹的收件人"void SetOwningNetConnection(FNetRefHandle Handle, uint32 ConnectionId);
// ═══════════════════════════════════════════════════════════════════// ⚡ 优先级设置// ═══════════════════════════════════════════════════════════════════
// 设置静态优先级 - "固定的发送优先级"void SetStaticPriority(FNetRefHandle Handle, float Priority);
// 设置动态优先级器 - "根据情况动态计算优先级"bool SetPrioritizer(FNetRefHandle Handle, FNetObjectPrioritizerHandle Prioritizer);
// ═══════════════════════════════════════════════════════════════════// 🔄 状态控制// ═══════════════════════════════════════════════════════════════════
// 强制立即更新 - "紧急包裹,马上发!"void ForceNetUpdate(FNetRefHandle Handle);
// 标记为脏 - "包裹内容变了,需要重新发送"void MarkDirty(FNetRefHandle Handle);
// 下一次更新时断开复制 - "最后一次发送后断开"void TearOffNextUpdate(FNetRefHandle Handle);📡 RPC 发送接口
远程过程调用(RPC)是网络游戏中调用远端函数的方式:
CPP
// 发送 RPC 到指定连接// 就像"给特定客户打电话通知"bool SendRPC(UObject* Object,
const UFunction* Function,
const void* Parameters,
uint32 ConnectionId);
// 多播 RPC - "群发通知"bool MulticastRPC(UObject* Object,
const UFunction* Function,
const void* Parameters);🌉 4.2 UReplicationBridge - 桥接基础设施
💡 它是什么?
UReplicationBridge 是连接游戏世界和网络复制系统的桥梁。它就像一个翻译官,把游戏对象"翻译"成网络系统能理解的格式。
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🎮 游戏世界 🌐 网络世界 │
│ ┌─────────┐ ┌─────────┐ │
│ │ AActor │ │ NetObj │ │
│ │ UObject │ ═══════════════► │ Handle │ │
│ │Component│ 🌉 Bridge │ State │ │
│ └─────────┘ └─────────┘ │
│ │
│ UReplicationBridge 负责这个转换过程 │
│ │
└─────────────────────────────────────────────────────────────────────┘🏠 类继承关系
PLAINTEXT
UObject
└── UReplicationBridge ◄── 基类:提供核心桥接功能
└── UObjectReplicationBridge ◄── 中间层:添加 UObject 特有功能
└── UEngineReplicationBridge ◄── 最终层:处理 Actor/Component
🔧 初始化流程源码分析
📖 源码位置: ReplicationBridge.cpp:348-415
CPP
// ReplicationBridge.cpp:348-415 - Initialize 方法void UReplicationBridge::Initialize(UReplicationSystem* InReplicationSystem){
using namespace UE::Net;
using namespace UE::Net::Private;
// 获取 ReplicationSystem 的内部实现
Private::FReplicationSystemInternal* ReplicationSystemInternal =
InReplicationSystem->GetReplicationSystemInternal();
// 1️⃣ 保存核心引用
ReplicationSystem = InReplicationSystem;
ReplicationProtocolManager = &ReplicationSystemInternal->GetReplicationProtocolManager();
ReplicationStateDescriptorRegistry = &ReplicationSystemInternal->GetReplicationStateDescriptorRegistry();
NetRefHandleManager = &ReplicationSystemInternal->GetNetRefHandleManager();
ObjectReferenceCache = &ReplicationSystemInternal->GetObjectReferenceCache();
Groups = &ReplicationSystemInternal->GetGroups();
// 2️⃣ 创建销毁信息协议(用于通知客户端对象被销毁)
{
const FReplicationFragments RegisteredFragments;
FCreateReplicationProtocolParameters CreateProtocolParams {
.bValidateProtocolId = false,
.TypeStatsIndex = GetReplicationSystem()->GetReplicationSystemInternal()
->GetNetTypeStats().GetOrCreateTypeStats(FName("DestructionInfo"))
};
DestructionInfoProtocol = ReplicationProtocolManager->CreateReplicationProtocol(
FReplicationProtocolManager::CalculateProtocolIdentifier(RegisteredFragments),
RegisteredFragments,
TEXT("InternalDestructionInfo"),
CreateProtocolParams
);
// 显式引用计数
DestructionInfoProtocol->AddRef();
}
}🔧 核心职责
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🌉 UReplicationBridge 职责 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1️⃣ 网络对象生命周期管理 │ │
│ │ • 创建网络对象 (CreateNetRefHandleFromRemote) │ │
│ │ • 销毁网络对象 (StopReplicatingNetRefHandle) │ │
│ │ • 分离实例 (DetachInstanceFromNetRefHandle) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 2️⃣ 子对象管理 │ │
│ │ • 注册子对象 │ │
│ │ • 维护父子关系 │ │
│ │ • 子对象生命周期跟随父对象 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 3️⃣ 关卡组管理 │ │
│ │ • 创建关卡组 (CreateLevelGroup) │ │
│ │ • 将对象添加到关卡组 │ │
│ │ • 支持关卡流送 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 4️⃣ 协议管理 │ │
│ │ • 管理复制协议 │ │
│ │ • 协议匹配和版本兼容 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘🔬 网络对象生命周期管理 - 深入源码分析
网络对象的生命周期管理是 UReplicationBridge 最核心的职责之一。让我们从源码层面深入理解三个关键方法:
📊 生命周期状态机
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────────────┐
│ 🔄 网络对象生命周期状态机 │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐
│ 🆕 未创建 │ │ 💀 已销毁 │
│ (NotExist) │ │ (Destroyed) │
└──────┬───────┘ └──────────────┘
│ ▲
│ CreateNetRefHandleFromRemote() │
│ (客户端) 或 │
│ InternalCreateNetObject() │
│ (服务器) │
▼ │
┌──────────────┐ AttachInstance() ┌──────────────┐
│ 📋 已注册 │ ─────────────────────────► │ 🔗 已绑定 │
│ (Registered) │ │ (Attached) │
│ │ ◄───────────────────────── │ │
│ • 有 Handle │ DetachInstance() │ • 有 Handle │
│ • 无实例绑定 │ │ • 有游戏对象 │
└──────┬───────┘ └──────┬───────┘
│ │
│ InternalDestroyNetObject() │ StopReplicating() /
│ (无实例时直接销毁) │ DestroyLocalNetHandle()
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ 💀 已销毁 (Destroyed) │
│ • Handle 被回收到空闲池 │
│ • 实例协议被销毁 │
│ • 子对象递归销毁 │
│ • 从所有组中移除 │
└─────────────────────────────────────────────────────────────────┘
1️⃣ CreateNetRefHandleFromRemote - 客户端创建网络对象
📖 源码位置: ObjectReplicationBridge.cpp:813-948
CPP
// ═══════════════════════════════════════════════════════════════════════════════// CreateNetRefHandleFromRemote - 客户端从服务器数据创建网络对象的核心函数// 参数说明:// - RootObjectOfSubObject: 如果是子对象,这是其根对象的句柄;否则无效// - WantedNetHandle: 服务器分配的期望网络句柄// - Context: 序列化上下文,包含网络流和连接信息// ═══════════════════════════════════════════════════════════════════════════════FReplicationBridgeCreateNetRefHandleResult UObjectReplicationBridge::CreateNetRefHandleFromRemote(
FNetRefHandle RootObjectOfSubObject,
FNetRefHandle WantedNetHandle,
FReplicationBridgeSerializationContext& Context){
LLM_SCOPE_BYTAG(IrisState); // 内存追踪标记,用于 LLM (Low Level Memory) 分析
using namespace UE::Net;
using namespace UE::Net::Private;
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段1: 读取工厂ID并获取对应的对象工厂 │
// └─────────────────────────────────────────────────────────────────────────┘
FNetBitStreamReader* Reader = Context.SerializationContext.GetBitStreamReader();
// 从网络流中读取工厂ID(使用固定位数,由 GetMaxBits() 决定)
// IntCastChecked 确保类型转换安全,溢出时会触发断言
const FNetObjectFactoryId FactoryId = IntCastChecked<FNetObjectFactoryId>(
Reader->ReadBits(FNetObjectFactoryRegistry::GetMaxBits()));
// 通过工厂ID获取对应的 UNetObjectFactory 实例
// 不同类型的对象(Actor、ActorComponent等)使用不同的工厂
UNetObjectFactory* Factory = GetNetFactory(FactoryId);
if (Factory == nullptr)
{
// 【关键检查】工厂必须有效,无效说明数据流可能损坏
// 设置错误标记后返回,上层会处理断开连接
Context.SerializationContext.SetError(GNetError_InvalidValue);
return FReplicationBridgeCreateNetRefHandleResult();
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段2: 读取并解析对象创建头(Header) │
// └─────────────────────────────────────────────────────────────────────────┘
// Header 包含创建对象所需的所有元数据(类信息、Archetype路径、协议ID等)
TUniquePtr<FNetObjectCreationHeader> Header = Factory->ReadHeader(
WantedNetHandle, Context.SerializationContext);
// 验证 Header 有效性和流状态
if (!Header.IsValid() || Context.SerializationContext.HasErrorOrOverflow())
{
return FReplicationBridgeCreateNetRefHandleResult();
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段3: 提取协议ID并准备Fragment注册上下文 │
// └─────────────────────────────────────────────────────────────────────────┘
// 协议ID用于验证客户端和服务器的复制协议是否匹配
const FReplicationProtocolIdentifier ReceivedProtocolId = Header->GetProtocolId();
// 创建 Fragment 注册上下文
// 【重要】CanReceive 标记表明远端对象只能接收数据,不能发送
// 这决定了后续创建的 Fragment 只包含接收相关的状态
FFragmentRegistrationContext FragmentRegistrationContext(
GetReplicationStateDescriptorRegistry(),
GetReplicationSystem(),
EReplicationFragmentTraits::CanReceive); // ← 仅接收模式
FReplicationProtocolManager* ProtocolManager = GetReplicationProtocolManager();
// 日志记录(仅在 Verbose 级别启用时)
if (UE_LOG_ACTIVE(LogIrisBridge, Verbose))
{
if (RootObjectOfSubObject.IsValid())
{
// 子对象日志
UE_LOG(LogIrisBridge, Verbose,
TEXT("CreateNetRefHandleFromRemote: SubObject: %s of RootObject: %s using header: %s"),
*WantedNetHandle.ToString(), *RootObjectOfSubObject.ToString(), ToCStr(Header->ToString()));
}
else
{
// 根对象日志
UE_LOG(LogIrisBridge, Verbose,
TEXT("CreateNetRefHandleFromRemote: RootObject: %s using header: %s"),
*WantedNetHandle.ToString(), ToCStr(Header->ToString()));
}
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段4: 实例化对象 │
// └─────────────────────────────────────────────────────────────────────────┘
// 【设计说明】当前总是立即实例化远端对象
// 未来计划支持延迟实例化,等到需要应用状态数据时再创建
// https://jira.it.epicgames.com/browse/UE-127369
UObject* InstancePtr = nullptr;
FReplicationBridgeCreateNetRefHandleResult CreateResult;
// 构建实例化上下文,包含:
// - WantedNetHandle: 期望的网络句柄
// - ResolveContext: 对象引用解析上下文
// - RootObjectOfSubObject: 父对象句柄(如果是子对象)
const UNetObjectFactory::FInstantiateContext InstantiateContext(
WantedNetHandle,
Context.SerializationContext.GetInternalContext()->ResolveContext,
RootObjectOfSubObject);
// 【核心调用】通过工厂从 Header 创建实际的 UObject 实例
// 工厂会根据 Header 中的类信息、Archetype 等创建正确类型的对象
const UNetObjectFactory::FInstantiateResult Result =
Factory->InstantiateReplicatedObjectFromHeader(InstantiateContext, Header.Get());
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段5: 实例化失败处理 │
// └─────────────────────────────────────────────────────────────────────────┘
if (!Result.Instance)
{
// 实例化失败,记录警告日志
if (UE_LOG_ACTIVE(LogIrisBridge, Warning) && !bSuppressCreateInstanceFailedEnsure)
{
if (RootObjectOfSubObject.IsValid())
{
UE_LOG(LogIrisBridge, Warning,
TEXT("CreateNetRefHandleFromRemote: Failed to instantiate SubObject NetHandle: %s of %s using header: %s"),
*WantedNetHandle.ToString(),
*PrintObjectFromNetRefHandle(RootObjectOfSubObject),
ToCStr(Header->ToString()));
}
else
{
UE_LOG(LogIrisBridge, Warning,
TEXT("CreateNetRefHandleFromRemote: Failed to instantiate RootObject NetHandle: %s using header: %s"),
*WantedNetHandle.ToString(), ToCStr(Header->ToString()));
}
}
// ensureMsgf 在开发版本中会触发断点,便于调试
// bSuppressCreateInstanceFailedEnsure 可以抑制此断言(某些情况下失败是预期的)
ensureMsgf(bSuppressCreateInstanceFailedEnsure,
TEXT("Failed to instantiate Handle: %s"), *WantedNetHandle.ToString());
return FReplicationBridgeCreateNetRefHandleResult();
}
InstancePtr = Result.Instance;
CreateResult.Flags |= Result.Flags; // 合并工厂返回的标志位
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段6: 注册复制 Fragment │
// └─────────────────────────────────────────────────────────────────────────┘
// 调用对象的 RegisterReplicationFragments,让对象注册其复制片段
// 每个 Fragment 代表对象的一部分可复制状态
CallRegisterReplicationFragments(InstancePtr, FragmentRegistrationContext,
EFragmentRegistrationFlags::None);
// 获取已注册的所有 Fragment
const FReplicationFragments& RegisteredFragments =
FFragmentRegistrationContextPrivateAccessor::GetReplicationFragments(FragmentRegistrationContext);
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段7: 协议查找与验证 │
// └─────────────────────────────────────────────────────────────────────────┘
// 获取 Archetype 或 CDO 作为协议缓存的 Key
// 【设计思想】同一 Archetype 的对象共享相同的复制协议,避免重复创建
const UObject* ArchetypeOrCDOUsedAsKey = InstancePtr->GetArchetype();
// 为当前实例创建 InstanceProtocol(实例级别的协议绑定)
FReplicationInstanceProtocolPtr InstanceProtocol(
ProtocolManager->CreateInstanceProtocol(RegisteredFragments));
// 尝试查找已存在的协议(使用 ProtocolId + Archetype 作为 Key)
const FReplicationProtocol* ReplicationProtocol =
ProtocolManager->GetReplicationProtocol(ReceivedProtocolId, ArchetypeOrCDOUsedAsKey);
if (!ReplicationProtocol)
{
// 协议不存在,创建新协议
// bValidateProtocolId = true 表示需要验证服务器发来的协议ID
FCreateReplicationProtocolParameters CreateProtocolParams {
.ArchetypeOrCDOUsedAsKey = ArchetypeOrCDOUsedAsKey,
.bValidateProtocolId = true
};
ReplicationProtocol = ProtocolManager->CreateReplicationProtocol(
ReceivedProtocolId,
RegisteredFragments,
*(InstancePtr->GetClass()->GetName()),
CreateProtocolParams);
}
else
{
// 协议已存在,验证本地 Fragment 是否与协议匹配
// 【重要】这是防止客户端/服务器代码不一致的关键检查
constexpr bool bDoNotLogErrors = false;
bool bIsValid = FReplicationProtocolManager::ValidateReplicationProtocol(
ReplicationProtocol, RegisteredFragments, bDoNotLogErrors);
if (!bIsValid)
{
ReplicationProtocol = nullptr; // 验证失败,置空触发错误处理
}
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 阶段8: 协议不匹配错误处理 │
// └─────────────────────────────────────────────────────────────────────────┘
if (!ReplicationProtocol)
{
// 【严重错误】协议不匹配,无法进行复制
// 通常是客户端/服务器版本不一致导致
UE_LOG(LogIris, Error,
TEXT("Protocol mismatch prevents binding %s to instanced object %s (CDO: %s)."),
*WantedNetHandle.ToString(), *GetNameSafe(InstancePtr), *GetNameSafe(ArchetypeOrCDOUsedAsKey));
// 调用调试工具输出详细的不匹配信息
if (UE_LOG_ACTIVE(LogIris, Error))
{
UE::Net::Private::ObjectBridgeDebugging::RemoteProtocolMismatchDetected(
ArchetypesAlreadyPrinted,
ReplicationSystem,
Context.ConnectionId,
RegisteredFragments,
ArchetypeOrCDOUsedAsKey,
InstancePtr);
}
// 广播严重错误事件,通知其他系统
FIrisDelegates::GetCriticalErrorDetectedDelegate().Broadcast(ReplicationSystem);
// 调用虚函数,允许子类自定义错误处理
OnProtocolMismatchDetected(WantedNetHandle);
}
else
{
// ┌─────────────────────────────────────────────────────────────────────┐
// │ 阶段9: 创建网络句柄并绑定实例 │
// └─────────────────────────────────────────────────────────────────────┘
// 在内部创建远端网络对象,分配实际的句柄
FNetRefHandle Handle = InternalCreateNetObjectFromRemote(
WantedNetHandle, ReplicationProtocol, FactoryId);
CreateResult.NetRefHandle = Handle;
if (Handle.IsValid())
{
// 注册远端实例,建立 Handle <-> Instance <-> Protocol 的三方绑定
RegisterRemoteInstance(Handle, InstancePtr, ReplicationProtocol,
InstanceProtocol.Get(), Context.ConnectionId);
// 【所有权转移】InstanceProtocol 的所有权已转移给系统
// Release() 释放 unique_ptr 的所有权但不销毁对象
(void)InstanceProtocol.Release();
// 调用工厂的后处理回调,允许执行额外的初始化逻辑
Factory->PostInstantiation(UNetObjectFactory::FPostInstantiationContext{
.Instance = InstancePtr,
.Header = Header.Get(),
.ConnectionId = Context.ConnectionId
});
}
}
return CreateResult;
}📌 代码流程(9个阶段):
阶段 | 说明 | 关键函数/操作 |
|---|---|---|
1️⃣ | 读取工厂ID |
|
2️⃣ | 读取创建头 |
|
3️⃣ | 准备Fragment上下文 |
|
4️⃣ | 实例化对象 |
|
5️⃣ | 失败处理 |
|
6️⃣ | 注册Fragment |
|
7️⃣ | 协议查找/创建 |
|
8️⃣ | 协议不匹配处理 |
|
9️⃣ | 创建句柄并绑定 |
|
🔑 关键设计要点:
CanReceive 标记:远端对象只能接收数据,不能主动发送
协议缓存:以
ProtocolId + Archetype为 Key 避免重复创建协议所有权转移:
InstanceProtocol.Release()将所有权交给系统管理工厂模式:不同类型对象(Actor、Component 等)使用不同工厂处理
2️⃣ StopReplicatingNetRefHandle - 停止复制(优雅清理)
当一个对象不再需要网络复制时(比如被销毁、或者主动停止复制):
📖 源码位置: ReplicationBridge.cpp:486-525
CPP
// ReplicationBridge.cpp - StopReplicatingNetRefHandle 实现void UReplicationBridge::StopReplicatingNetRefHandle(FNetRefHandle Handle){
// ========== 前置检查 ==========
if (!Handle.IsValid())
{
return;
}
const FInternalNetRefIndex InternalIndex = NetRefHandleManager->GetInternalIndex(Handle);
if (InternalIndex == InvalidInternalNetRefIndex)
{
return; // 句柄无效或已被销毁
}
// ========== 获取对象数据 ==========
FReplicatedObjectData* ObjectData = NetRefHandleManager->GetReplicatedObjectDataNoCheck(InternalIndex);
// 检查是否是根对象
// 💡 只有根对象才能主动停止复制,子对象跟随父对象
if (ObjectData->SubObjectRootIndex != InvalidInternalNetRefIndex)
{
// 这是子对象,应该通过父对象的销毁流程来处理
UE_LOG(LogIris, Warning,
TEXT("StopReplicatingNetRefHandle called on SubObject %s, should destroy through root!"),
*Handle.ToString());
return;
}
// ========== 确定销毁标志 ==========
EEndReplicationFlags EndFlags = EEndReplicationFlags::Destroy;
// 如果是本地权威对象(服务器上创建的),需要通知客户端销毁
if (ObjectData->bHasLocalAuthority)
{
EndFlags |= EEndReplicationFlags::SendDestroyToClients;
}
// ========== 执行销毁 ==========
DestroyLocalNetHandle(Handle, EndFlags);
}🔍 EEndReplicationFlags 标志详解:
CPP
// 来自 ReplicationBridge.henum class EEndReplicationFlags : uint32
{
None = 0,
// 🗑️ 销毁网络句柄(释放 ID 回到空闲池)
DestroyNetHandle = 1 << 0,
// 🧹 清除 Push Model 的 NetPushId
ClearNetPushId = 1 << 1,
// 📡 发送销毁通知给所有客户端
SendDestroyToClients = 1 << 2,
// 🔄 立即刷新,不等待下一帧
FlushDestroyImmediate = 1 << 3,
// 💨 不调用子类的清理回调
SkipPendingDestroy = 1 << 4,
// 常用组合
Destroy = DestroyNetHandle | ClearNetPushId,
DestroyAndNotify = Destroy | SendDestroyToClients,
};📊 停止复制的触发场景:
场景 | 触发方式 | EndFlags |
|---|---|---|
Actor 被销毁 |
|
|
手动停止复制 | 直接调用 |
|
关卡卸载 | 关卡组禁用 | 批量 |
服务器关闭 | 清理所有对象 |
|
3️⃣ DetachInstanceFromNetRefHandle - 分离实例(高级用法)
分离与销毁不同:分离只是解除游戏对象与网络句柄的绑定,句柄本身可以继续存在。
📖 源码位置: ReplicationBridge.cpp:467-484
CPP
// ReplicationBridge.cpp - InternalDetachInstanceFromNetRefHandle 实现void UReplicationBridge::InternalDetachInstanceFromNetRefHandle(FNetRefHandle RefHandle){
const FInternalNetRefIndex InternalIndex = NetRefHandleManager->GetInternalIndex(RefHandle);
// ========== 分离实例协议 ==========
// DetachInstanceProtocol 返回之前绑定的协议(如果有的话)
FReplicationInstanceProtocol* InstanceProtocol =
const_cast<FReplicationInstanceProtocol*>(
NetRefHandleManager->DetachInstanceProtocol(InternalIndex)
);
if (InstanceProtocol)
{
UE_LOG_REPLICATIONBRIDGE(Verbose,
TEXT("InternalDetachInstanceFromNetHandle Detached: %s from (InternalIndex: %u)"),
*RefHandle.ToString(), InternalIndex);
// ========== 解绑实例协议 ==========
// 如果协议已经绑定到 ReplicationState,需要解绑
if (EnumHasAnyFlags(InstanceProtocol->InstanceTraits, EReplicationInstanceProtocolTraits::IsBound))
{
// 解除 Fragment 与游戏对象的绑定
// 这会调用每个 Fragment 的 Unbind 方法
FReplicationInstanceOperationsInternal::UnbindInstanceProtocol(
InstanceProtocol,
NetRefHandleManager->GetReplicatedObjectDataNoCheck(InternalIndex).Protocol
);
}
// ========== 销毁实例协议 ==========
// 释放 InstanceProtocol 占用的内存
ReplicationProtocolManager->DestroyInstanceProtocol(InstanceProtocol);
}
// 注意:此时 NetRefHandle 仍然有效!
// 只是没有游戏对象与之关联了
}🤔 为什么需要"分离"而不是直接"销毁"?
PLAINTEXT
场景:对象池 (Object Pooling)
───────────────────────────────────────────────────────────────────
传统做法(无对象池):
┌─────────┐ 销毁 ┌─────────┐ 创建 ┌─────────┐
│ 敌人A │ ───────► │ 空 │ ───────► │ 敌人B │
│ Handle=1│ │ │ │ Handle=2│
└─────────┘ └─────────┘ └─────────┘
↑ ↑
└── 完全销毁,句柄回收 新句柄,重新同步所有属性
对象池做法(使用 Detach):
┌─────────┐ 分离 ┌─────────┐ 重新附加 ┌─────────┐
│ 敌人A │ ───────► │ 池对象 │ ─────────► │ 敌人B │
│ Handle=1│ │ Handle=1│ │ Handle=1│
│ 实例=X │ │ 无实例 │ │ 实例=Y │
└─────────┘ └─────────┘ └─────────┘
↑ ↑ ↑
│ │ │
│ 句柄保留, 重用句柄,
│ 等待重用 只同步变化的属性
优势:
✅ 减少句柄分配/释放开销
✅ 客户端可以复用已有对象
✅ 减少网络流量(不需要发送完整的创建信息)📊 Attach vs Detach 操作对比:
操作 | 方法 | 效果 |
|---|---|---|
附加 |
| 将游戏对象绑定到网络句柄,开始追踪属性变化 |
分离 |
| 解除绑定,停止追踪,但句柄保留 |
销毁 |
| 分离 + 销毁句柄 + 通知客户端 |
🔄 完整生命周期流程图
PLAINTEXT
服务器端 客户端
═══════════════════════════════════════════════════════════════════════
1️⃣ 创建阶段
┌──────────────────┐
│ SpawnActor() │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ StartReplicating │
│ • 分配 Handle │ ─────── 📡 创建数据包 ──────►┌──────────────────┐
│ • 创建协议 │ │CreateNetRefHandle│
│ • 附加实例 │ │FromRemote │
└──────────────────┘ │ • 使用服务器Handle│
│ • 创建本地对象 │
└──────────────────┘
2️⃣ 运行阶段
┌──────────────────┐ ┌──────────────────┐
│ Poll 检测变化 │ │ │
│ • 属性变化 │ ─────── 📡 属性数据包 ──────►│ Apply 应用状态 │
│ • 标记脏位 │ │ • 更新属性 │
└──────────────────┘ │ • 调用 RepNotify │
└──────────────────┘
3️⃣ 销毁阶段
┌──────────────────┐
│ DestroyActor() │
└────────┬─────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐
│StopReplicating │ │ │
│ • 分离实例 │ ─────── 📡 销毁数据包 ──────►│ 收到销毁通知 │
│ • 销毁子对象 │ │ • 分离实例 │
│ • 释放句柄 │ │ • 销毁本地对象 │
│ • 发送销毁通知 │ │ • 释放句柄 │
└──────────────────┘ └──────────────────┘👶 子对象管理
游戏对象通常有层级关系,比如一个角色(Actor)可能有多个组件(Component):
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 👨👧👦 父子对象关系 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ 🧑 Character │ ◄── 根对象 (RootObject)
│ (AActor) │
└────────┬────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 🎒 Inventory │ │ 🏃 Movement │ │ ❤️ Health │
│ Component │ │ Component │ │ Component │
└─────────────┘ └─────────────┘ └─────────────┘
▲ ▲ ▲
│ │ │
└─────────────────┴─────────────────┘
│
子对象 (SubObject)
跟随父对象一起复制关键规则:
📌 子对象的生命周期跟随父对象
📌 父对象被销毁时,子对象也会被销毁
📌 子对象的休眠状态继承自父对象
🗺️ 关卡组管理
关卡组用于支持关卡流送(Level Streaming),让大型开放世界游戏能够动态加载/卸载区域:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🗺️ 关卡组示意图 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 🌍 游戏世界 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 🏔️ 区域A │ │ 🌲 区域B │ │ 🏖️ 区域C │ │
│ │ Level_A │ │ Level_B │ │ Level_C │ │
│ │ │ │ │ │ │ │
│ │ • 敌人1 │ │ • 敌人3 │ │ • 敌人5 │ │
│ │ • 敌人2 │ │ • 敌人4 │ │ • NPC1 │ │
│ │ • 宝箱1 │ │ • 道具1 │ │ • 宝箱2 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 📁 关卡组映射 │
│ │
│ LevelGroups = { │
│ Level_A → GroupHandle_A (包含: 敌人1, 敌人2, 宝箱1) │
│ Level_B → GroupHandle_B (包含: 敌人3, 敌人4, 道具1) │
│ Level_C → GroupHandle_C (包含: 敌人5, NPC1, 宝箱2) │
│ } │
└─────────────────────────────────────────────────────────────┘
当玩家离开某个区域时,可以禁用该关卡组的复制,节省带宽!
🔗 4.3 UObjectReplicationBridge - 对象管理专家
💡 它是什么?
UObjectReplicationBridge 在 UReplicationBridge 的基础上,添加了UObject 特有的复制功能,比如休眠管理、轮询频率控制等。
🛏️ 休眠 (Dormancy) 管理
休眠是一种优化技术:当对象长时间不变化时,让它"睡觉",不再占用网络带宽。
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🛏️ 休眠状态机 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ 😴 休眠中 │
│ (Dormant) │
└────────┬────────┘
│
FlushDormant()
或属性变化
│
▼
┌─────────────────┐
│ 😃 活跃中 │
│ (Awake) │
└────────┬────────┘
│
SetWantsToBeDormant(true)
且无属性变化
│
▼
┌─────────────────┐
│ 😴 休眠中 │
│ (Dormant) │
└─────────────────┘🎮 生活化比喻
想象你在玩一个开放世界游戏:
活跃状态 😃:你正在和一个NPC战斗,它的位置、血量每帧都在变化,需要实时同步
休眠状态 😴:远处的一棵树,它几乎不会变化,没必要每帧都检查和发送
⏱️ 轮询频率控制源码分析
不是所有对象都需要每帧检查变化。轮询频率控制让我们可以为不同对象设置不同的检查频率。
📖 源码位置: ObjectReplicationBridge.cpp:61-94 - 控制变量定义
CPP
// ObjectReplicationBridge.cpp:61-94 - 轮询相关控制变量static bool bUseFrequencyBasedPolling = true;
static FAutoConsoleVariableRef CVarUseFrequencyBasedPolling(
TEXT("net.Iris.UseFrequencyBasedPolling"),
bUseFrequencyBasedPolling,
TEXT("Whether to use frequency based polling or not. Default is true.")
);
static bool bUseDormancyToFilterPolling = true;
static FAutoConsoleVariableRef CVarUseDormancyToFilterPolling(
TEXT("net.Iris.UseDormancyToFilterPolling"),
bUseDormancyToFilterPolling,
TEXT("Whether we should use dormancy to filter out objects that we should not poll. Default is true.")
);
static bool bAllowPollPeriodOverrides = true;
static FAutoConsoleVariableRef CVarAllowPollPeriodOverrides(
TEXT("net.Iris.AllowPollPeriodOverrides"),
bAllowPollPeriodOverrides,
TEXT("Whether we allow poll period overrides set in ObjectReplicationBridgeConfig. Default is true.")
);
static bool bEnableForceNetUpdate = false;
static FAutoConsoleVariableRef CVarEnableForceNetUpdate(
TEXT("net.Iris.EnableForceNetUpdate"),
bEnableForceNetUpdate,
TEXT("When true the system only allows ForceNetUpdate to skip the poll frequency of objects. "
"When false any MarkDirty object will be immediately polled.")
);PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ ⏱️ 轮询频率示意 │
└─────────────────────────────────────────────────────────────────────┘
时间轴 → 帧1 帧2 帧3 帧4 帧5 帧6 帧7 帧8
│ │ │ │ │ │ │ │
─────────────────────────────────────────────────────────────
玩家角色 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
(每帧轮询) │ │ │ │ │ │ │ │
敌人AI ✓ ✓ ✓ ✓
(每2帧轮询) │ │ │ │
环境物体 ✓ ✓
(每4帧轮询) │ │
背景装饰 ✓
(每8帧轮询) │🌳 StartReplicatingRootObject 源码分析
这是开始复制根对象的核心方法,展示了如何配置轮询频率、优先级和过滤器。
📖 源码位置: ObjectReplicationBridge.cpp:421-493
CPP
// ObjectReplicationBridge.cpp:421-493 - StartReplicatingRootObject 实现FNetRefHandle UObjectReplicationBridge::StartReplicatingRootObject(
UObject* Instance,
const FRootObjectReplicationParams& Params,
FNetObjectFactoryId NetFactoryId){
LLM_SCOPE_BYTAG(IrisState);
// 1️⃣ 设置复制特性
EReplicationFragmentTraits Traits = EReplicationFragmentTraits::CanReplicate;
Traits |= Params.bNeedsPreUpdate ? EReplicationFragmentTraits::NeedsPreSendUpdate : EReplicationFragmentTraits::None;
Traits |= Params.bNeedsWorldLocationUpdate ? EReplicationFragmentTraits::NeedsWorldLocationUpdate : EReplicationFragmentTraits::None;
// 2️⃣ 注册对象
FNetRefHandle RefHandle = StartReplicatingNetObject(Instance, Traits, NetFactoryId);
if (!RefHandle.IsValid())
{
return FNetRefHandle::GetInvalid();
}
// 3️⃣ 配置根对象
const FInternalNetRefIndex ObjectIndex = NetRefHandleManager->GetInternalIndex(RefHandle);
// 4️⃣ 设置轮询帧周期
float PollFrequency = Params.PollFrequency;
FindOrCachePollFrequency(Instance->GetClass(), PollFrequency);
uint8 PollFramePeriod = ConvertPollFrequencyIntoFrames(PollFrequency);
PollFrequencyLimiter->SetPollFramePeriod(ObjectIndex, PollFramePeriod);
// 5️⃣ 初始化世界位置缓存(如果需要)
if (Params.bNeedsWorldLocationUpdate)
{
FWorldLocations& WorldLocations = ReplicationSystem->GetReplicationSystemInternal()->GetWorldLocations();
WorldLocations.InitObjectInfoCache(ObjectIndex);
}
// 6️⃣ 设置优先级器
const bool bRequireForceEnabled = Params.StaticPriority > 0.0f;
const FNetObjectPrioritizerHandle PrioritizerHandle = GetPrioritizer(Instance->GetClass(), bRequireForceEnabled);
if (Params.StaticPriority > 0.0f && PrioritizerHandle == InvalidNetObjectPrioritizerHandle)
{
// 使用静态优先级
ReplicationSystem->SetStaticPriority(RefHandle, Params.StaticPriority);
}
else
{
if (PrioritizerHandle != InvalidNetObjectPrioritizerHandle)
{
ReplicationSystem->SetPrioritizer(RefHandle, PrioritizerHandle);
}
else if (Params.bNeedsWorldLocationUpdate || HasRepTag(ReplicationSystem->GetReplicationProtocol(RefHandle), RepTag_WorldLocation))
{
// 使用默认空间优先级器
ReplicationSystem->SetPrioritizer(RefHandle, DefaultSpatialNetObjectPrioritizerHandle);
}
}
// 7️⃣ 设置动态过滤器
AssignDynamicFilter(Instance, Params, RefHandle);
// 8️⃣ 空间过滤的非休眠对象需要频繁更新世界位置
if (Params.bNeedsWorldLocationUpdate && !Params.bIsDormant)
{
OptionallySetObjectRequiresFrequentWorldLocationUpdate(RefHandle, true);
}
return RefHandle;
}👶 StartReplicatingSubObject 源码分析
子对象的复制流程与根对象略有不同。
📖 源码位置: ObjectReplicationBridge.cpp:531-575
CPP
// ObjectReplicationBridge.cpp:531-575 - StartReplicatingSubObject 实现FNetRefHandle UObjectReplicationBridge::StartReplicatingSubObject(
UObject* Instance,
const FSubObjectReplicationParams& Params,
FNetObjectFactoryId NetFactoryId){
LLM_SCOPE_BYTAG(IrisState);
// ✅ 验证:所有者必须已经在复制
checkf(IsReplicatedHandle(Params.RootObjectHandle),
TEXT("Owner %s must be replicated for subobject %s to replicate."),
*GetNameSafe(Owner), *GetNameSafe(Instance));
// ✅ 验证:不能复制原型或默认对象
checkf(!Instance->HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject),
TEXT("Iris cannot replicate subobject %s because it's an Archetype or DefaultObject"),
*GetNameSafe(Instance));
// 🔍 检查是否已经在复制
FNetRefHandle SubObjectRefHandle = GetReplicatedRefHandle(Instance);
if (SubObjectRefHandle.IsValid())
{
// 验证现有对象是所有者的子对象
check(Params.RootObjectHandle == LocalNetRefHandleManager.GetRootObjectOfSubObject(SubObjectRefHandle));
return SubObjectRefHandle;
}
// 📦 开始复制网络对象
const EReplicationFragmentTraits Traits = EReplicationFragmentTraits::CanReplicate;
SubObjectRefHandle = StartReplicatingNetObject(Instance, Traits, NetFactoryId);
if (!SubObjectRefHandle.IsValid())
{
return FNetRefHandle::GetInvalid();
}
// 🔗 添加子对象关系
InternalAddSubObject(Params.RootObjectHandle, SubObjectRefHandle,
Params.InsertRelativeToSubObjectHandle, Params.InsertionOrder);
UE_LOG_OBJECTREPLICATIONBRIDGE(Verbose, TEXT("StartReplicatingSubObject Added %s RelativeToSubObjectHandle %s"),
*PrintObjectFromNetRefHandle(SubObjectRefHandle),
*PrintObjectFromNetRefHandle(Params.InsertRelativeToSubObjectHandle));
// ⏱️ 子对象应该总是与所有者一起轮询
SetPollWithObject(Params.RootObjectHandle, SubObjectRefHandle);
// 🛏️ 从所有者复制休眠状态
SetSubObjectDormancyStatus(SubObjectRefHandle, Params.RootObjectHandle);
return SubObjectRefHandle;
}🌳 RootObject 与 SubObject
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🌳 对象层级结构 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 🌳 RootObject (根对象) │
│ │
│ 特点: │
│ • 独立存在,有自己的网络句柄 │
│ • 可以设置过滤器和优先级器 │
│ • 控制自己和子对象的休眠状态 │
│ • 例如:AActor, APawn, ACharacter │
└────────────────────────┬────────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 🍃 SubObject │ │ 🍃 SubObject │ │ 🍃 SubObject │
│ (子对象) │ │ (子对象) │ │ (子对象) │
│ │ │ │ │ │
│ 特点: │ │ 特点: │ │ 特点: │
│ • 依附于根 │ │ • 依附于根 │ │ • 依附于根 │
│ • 共享过滤 │ │ • 共享过滤 │ │ • 共享过滤 │
│ • 继承休眠 │ │ • 继承休眠 │ │ • 继承休眠 │
│ │ │ │ │ │
│ 例如: │ │ 例如: │ │ 例如: │
│ UComponent │ │ UInventory │ │ UAbility │
└─────────────┘ └─────────────┘ └─────────────┘🔗 依赖对象管理
有时候,一个对象的复制依赖于另一个对象:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🔗 依赖关系示例 │
└─────────────────────────────────────────────────────────────────────┘
场景:玩家拿起一把武器
┌─────────────┐ ┌─────────────┐
│ 🧑 Player │ ──────► │ 🗡️ Weapon │
│ │ 持有 │ │
└─────────────┘ └─────────────┘
│ │
│ │
▼ ▼
必须先复制 Player 才能复制 Weapon
(因为 Weapon 引用了 Player 作为持有者)
依赖调度提示:
CPP
enum class EDependentObjectSchedulingHint : uint8
{
// 默认:依赖对象在同一批次中复制
Default,
// 在父对象之前复制
ScheduleBeforeParent,
// 在父对象之后复制
ScheduleAfterParent,
};🏭 4.4 UEngineReplicationBridge - UE引擎的翻译官
💡 它是什么?
UEngineReplicationBridge 是最接近游戏代码的桥接层,专门处理 Actor 和 Component 的复制。它知道如何与 UE 的 NetDriver 系统对接。
🎭 Actor 复制接口
CPP
FNetRefHandle UEngineReplicationBridge::StartReplicatingActor(
AActor* Actor,
const FActorReplicationParams& Params){
// ✅ 验证检查
if (!bIsNetActor ||
Actor->GetLocalRole() != ROLE_Authority || // 必须是权威端
!Actor->GetIsReplicated()) // 必须标记为可复制
{
return FNetRefHandle::GetInvalid();
}
// 📋 创建根对象参数
FRootObjectReplicationParams RootObjectParams = {
.bNeedsPreUpdate = 1U, // 需要预更新
.bNeedsWorldLocationUpdate = 1U, // 需要位置更新
.bIsDormant = Actor->NetDormancy > DORM_Awake, // 初始休眠状态
.StaticPriority = (Actor->bAlwaysRelevant || Actor->bOnlyRelevantToOwner)
? Actor->NetPriority : 0.0f,
.PollFrequency = Actor->GetNetUpdateFrequency() // 轮询频率
};
// 🎫 开始复制根对象
FNetRefHandle ActorRefHandle = StartReplicatingRootObject(
Actor,
RootObjectParams,
ActorFactoryId
);
// 🎯 设置所有者过滤(如果只对所有者可见)
if (Actor->bOnlyRelevantToOwner && !Actor->bAlwaysRelevant)
{
GetReplicationSystem()->SetFilter(ActorRefHandle, ToOwnerFilterHandle);
}
// 🛏️ 设置休眠状态
if (Dormancy > DORM_Awake)
{
SetObjectWantsToBeDormant(ActorRefHandle, true);
}
// 🗺️ 添加到关卡组
AddActorToLevelGroup(Actor);
// 🔧 复制子对象和组件
// ...
return ActorRefHandle;
}📖 源码位置: Engine/Source/Runtime/Engine/Private/Net/Iris/ReplicationSystem/EngineReplicationBridge.cpp:204-381
🔧 Component 复制接口
组件作为 Actor 的子对象进行复制:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🔧 Component 复制流程 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ 🎭 AActor │
│ (已复制) │
└────────┬────────┘
│
│ GetReplicatedComponents()
│ 获取需要复制的组件列表
▼
┌─────────────────────────────────────────────────────────────┐
│ 遍历每个组件 │
│ │
│ for (UActorComponent* Component : ReplicatedComponents) │
│ { │
│ // 作为子对象开始复制 │
│ StartReplicatingComponent(ActorHandle, Component); │
│ } │
└─────────────────────────────────────────────────────────────┘🔌 与 NetDriver 的集成
UEngineReplicationBridge 需要与传统的 UNetDriver 系统协同工作:
CPP
void UEngineReplicationBridge::SetNetDriver(UNetDriver* const InNetDriver){
// 🔌 断开旧连接
if (NetDriver)
{
NetDriver->OnNetServerMaxTickRateChanged.RemoveAll(this);
NetDriver->GetOnNetUpdateFrequencyChanged().RemoveAll(this);
}
NetDriver = InNetDriver;
if (InNetDriver != nullptr)
{
// ⏱️ 同步 Tick 速率
SetMaxTickRate(static_cast<float>(
FPlatformMath::Max(InNetDriver->GetNetServerMaxTickRate(), 0)
));
// 📡 监听速率变化事件
InNetDriver->OnNetServerMaxTickRateChanged.AddUObject(
this,
&UEngineReplicationBridge::OnMaxTickRateChanged
);
InNetDriver->GetOnNetUpdateFrequencyChanged().AddUObject(
this,
&UEngineReplicationBridge::OnNetUpdateFrequencyChanged
);
}
}📖 源码位置: Engine/Source/Runtime/Engine/Private/Net/Iris/ReplicationSystem/EngineReplicationBridge.cpp:649-677
🗺️ 关卡流送支持
当关卡被加载或卸载时,需要相应地管理网络对象:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🗺️ 关卡流送与网络复制 │
└─────────────────────────────────────────────────────────────────────┘
玩家移动方向 →
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 🌲 Level_A │ │ 🏔️ Level_B │ │ 🏖️ Level_C │
│ (卸载) │ │ (当前) │ │ (加载) │
│ │ │ │ │ │
│ 停止复制 │ │ 正常复制 │ │ 开始复制 │
│ 组内对象 │ │ 组内对象 │ │ 组内对象 │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │
│ │
└───────────────────────────────────┘
关卡组控制复制状态🎫 4.5 FNetRefHandleManager - 身份证管理局
💡 它是什么?
FNetRefHandleManager 负责为每个网络对象分配唯一标识符(句柄),并管理对象的内部索引。就像身份证管理局给每个公民发放身份证号。
🏗️ 核心数据结构与初始化
📖 源码位置: NetRefHandleManager.cpp:31-80
CPP
// NetRefHandleManager.cpp:31-80 - Init 方法完整实现void FNetRefHandleManager::Init(const FInitParams& InitParams){
// 1️⃣ 配置最大对象数(向上取整到字边界)
MaxActiveObjectCount = FNetBitArray::RoundUpToMaxWordBitCount(InitParams.MaxActiveObjectCount);
InternalNetRefIndexGrowSize = InitParams.InternalNetRefIndexGrowSize > 0
? FNetBitArray::RoundUpToMaxWordBitCount(InitParams.InternalNetRefIndexGrowSize)
: MaxActiveObjectCount;
ReplicationSystemId = InitParams.ReplicationSystemId;
// 2️⃣ 预分配分块数组数量(最少为 1,用于 InvalidInternalIndex)
uint32 PreAllocatedNetChunkedArrayCount = FMath::Clamp(InitParams.NetChunkedArrayCount, 1U, MaxActiveObjectCount);
HighestNetChunkedArrayInternalIndex = PreAllocatedNetChunkedArrayCount - 1;
// 3️⃣ 初始化内部索引配置
CurrentMaxInternalNetRefIndex = InitParams.InternalNetRefIndexInitSize > 0
? FMath::Min(InitParams.InternalNetRefIndexInitSize, MaxActiveObjectCount)
: MaxActiveObjectCount;
CurrentMaxInternalNetRefIndex = FNetBitArray::RoundUpToMaxWordBitCount(CurrentMaxInternalNetRefIndex);
UE_LOG(LogIris, Log,
TEXT("NetRefHandleManager: Configured with MaxActiveObjectCount=%d, MaxInternalNetRefIndex: %u, Grow=%u"),
MaxActiveObjectCount, CurrentMaxInternalNetRefIndex, InternalNetRefIndexGrowSize);
// 4️⃣ 初始化分块数组(TNetChunkedArray)
ReplicatedObjectData = TNetChunkedArray<FReplicatedObjectData>(PreAllocatedNetChunkedArrayCount, EInitMemory::Constructor);
ReplicatedObjectRefCount = TNetChunkedArray<uint16>(PreAllocatedNetChunkedArrayCount, EInitMemory::Zero);
ReplicatedObjectStateBuffers = TNetChunkedArray<uint8*>(PreAllocatedNetChunkedArrayCount, EInitMemory::Zero);
ReplicatedInstances = TNetChunkedArray<TObjectPtr<UObject>>(PreAllocatedNetChunkedArrayCount, EInitMemory::Zero);
// 5️⃣ 为 InvalidInternalIndex 初始化默认数据
ReplicatedObjectData[InvalidInternalIndex] = FReplicatedObjectData();
// 6️⃣ 初始化所有位数组
{
InitNetBitArray(&ScopeFrameData.CurrentFrameScopableInternalIndices);
InitNetBitArray(&ScopeFrameData.PrevFrameScopableInternalIndices);
InitNetBitArray(&GlobalScopableInternalIndices);
InitNetBitArray(&RelevantObjectsInternalIndices);
InitNetBitArray(&PolledObjectsInternalIndices);
InitNetBitArray(&DirtyObjectsToQuantize);
InitNetBitArray(&AssignedInternalIndices);
InitNetBitArray(&SubObjectInternalIndices);
InitNetBitArray(&DependentObjectInternalIndices);
InitNetBitArray(&ObjectsWithDependentObjectsInternalIndices);
InitNetBitArray(&DestroyedStartupObjectInternalIndices);
InitNetBitArray(&WantToBeDormantInternalIndices);
InitNetBitArray(&ObjectsWithPreUpdate);
InitNetBitArray(&DormantObjectsPendingFlushNet);
}
// 7️⃣ 标记无效索引为已使用
AssignedInternalIndices.SetBit(0);
}PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐│ 🎫 FNetRefHandleManager 内部结构 │└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📊 计数器 │
│ MaxActiveObjectCount = 65536 // 最大对象数 │
│ ActiveObjectCount = 1234 // 当前活跃对象数 │
│ NextStaticHandleIndex = 100 // 下一个静态句柄ID │
│ NextDynamicHandleIndex = 5000 // 下一个动态句柄ID │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 🗺️ 映射表 │
│ │
│ RefHandleToInternalIndex: │
│ ┌──────────────┬─────────────────┐ │
│ │ NetRefHandle │ InternalIndex │ │
│ ├──────────────┼─────────────────┤ │
│ │ Handle_001 │ 0 │ │
│ │ Handle_002 │ 1 │ │
│ │ Handle_003 │ 2 │ │
│ │ ... │ ... │ │
│ └──────────────┴─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📝 位数组 (BitArrays) │
│ │
│ AssignedInternalIndices: [1,1,1,0,0,1,1,0,...] │
│ ↑ 已分配的索引 │
│ │
│ WantToBeDormantIndices: [0,0,1,0,0,0,1,0,...] │
│ ↑ 想要休眠的对象 │
│ │
│ SubObjectInternalIndices: [0,1,0,0,1,1,0,0,...] │
│ ↑ 子对象标记 │
│ │
│ DirtyObjectsToQuantize: [1,0,1,0,0,1,0,0,...] │
│ ↑ 待量化的脏对象 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📦 分块数组 (Chunked Arrays) │
│ │
│ ReplicatedObjectData[]: 对象元数据 │
│ ReplicatedObjectStateBuffers[]: 状态缓冲区指针 │
│ ReplicatedObjectRefCount[]: 引用计数 │
│ ReplicatedInstances[]: UObject 实例指针 │
└─────────────────────────────────────────────────────────────┘🎫 句柄分配机制源码分析
📖 源码位置: NetRefHandleManager.cpp:169-186
CPP
// NetRefHandleManager.cpp:169-186 - 获取下一个句柄IDuint64 FNetRefHandleManager::GetNextNetRefHandleId(uint64 HandleId) const{
// 由于我们使用索引中的最低位来指示句柄是静态还是动态的,
// 所以不能使用所有位作为索引
constexpr uint64 NetHandleIdIndexBitMask = (1ULL << (FNetRefHandle::IdBits - 1)) - 1;
uint64 NextHandleId = (HandleId + 1) & NetHandleIdIndexBitMask;
if (NextHandleId == 0)
{
++NextHandleId; // 跳过 0,因为 0 表示无效句柄
}
return NextHandleId;
}
// NetRefHandleManager.cpp:182-186 - 获取下一个空闲内部索引FInternalNetRefIndex FNetRefHandleManager::GetNextFreeInternalIndex() const{
// 在位数组中找到第一个为 0 的位(表示未分配)
const uint32 NextFreeIndex = AssignedInternalIndices.FindFirstZero();
return NextFreeIndex != FNetBitArray::InvalidIndex ? NextFreeIndex : InvalidInternalIndex;
}📦 创建网络对象源码分析
📖 源码位置: NetRefHandleManager.cpp:188-268
CPP
// NetRefHandleManager.cpp:188-268 - InternalCreateNetObject 完整实现FInternalNetRefIndex FNetRefHandleManager::InternalCreateNetObject(
const FNetRefHandle NetRefHandle,
const FNetHandle GlobalHandle,
const FReplicationProtocol* ReplicationProtocol){
// 1️⃣ 检查是否达到最大对象数
if (ActiveObjectCount >= MaxActiveObjectCount)
{
UE_LOG(LogIris, Error, TEXT("NetRefHandleManager: Maximum active object count reached (%d/%d)."),
ActiveObjectCount, MaxActiveObjectCount);
ensureMsgf(false, TEXT("NetRefHandleManager: Maximum active object count reached (%d/%d)."),
ActiveObjectCount, MaxActiveObjectCount);
return InvalidInternalIndex;
}
// 2️⃣ 验证句柄是否已存在
if (RefHandleToInternalIndex.Contains(NetRefHandle))
{
ensureMsgf(false, TEXT("NetRefHandleManager::InternalCreateNetObject %s already exists"),
*NetRefHandle.ToString());
return InvalidInternalIndex;
}
// 3️⃣ 获取下一个空闲索引
uint32 InternalIndex = GetNextFreeInternalIndex();
// 4️⃣ 如果没有空闲索引,尝试增长列表
if (InternalIndex == InvalidInternalIndex)
{
InternalIndex = GrowNetObjectLists();
// 如果无法继续增长,终止进程
if (InternalIndex == InvalidInternalIndex)
{
UE_LOG(LogIris, Fatal,
TEXT("NetRefHandleManager: Hit the maximum limit of active replicated objects: %u. "
"Aborting since we cannot replicate %s"),
MaxActiveObjectCount, ReplicationProtocol->DebugName->Name);
return InvalidInternalIndex;
}
}
UE_LOG(LogIris, Verbose, TEXT("FNetRefHandleManager::InternalCreateNetObject: (InternalIndex: %u) (%s)"),
InternalIndex, *NetRefHandle.ToString());
// 5️⃣ 如果需要,增长分块数组缓冲区
if (InternalIndex > HighestNetChunkedArrayInternalIndex)
{
GrowNetChunkedArrayBuffers(InternalIndex);
}
// 6️⃣ 存储对象数据
FReplicatedObjectData& Data = ReplicatedObjectData[InternalIndex];
Data = FReplicatedObjectData(); // 重置为默认值
Data.RefHandle = NetRefHandle;
Data.NetHandle = GlobalHandle;
Data.Protocol = ReplicationProtocol;
Data.InstanceProtocol = nullptr;
Data.ReceiveStateBuffer = nullptr;
Data.bShouldPropagateChangedStates = 1U; // 应该传播变更
Data.bNeedsFullCopyAndQuantize = 1U; // 需要完整复制
Data.bWantsFullPoll = 1U; // 需要完整轮询
// 7️⃣ 清除相关位数组
ObjectsWithPreUpdate.ClearBit(InternalIndex);
ReplicatedObjectStateBuffers[InternalIndex] = nullptr;
++ActiveObjectCount;
// 8️⃣ 添加映射:NetRefHandle -> InternalIndex
RefHandleToInternalIndex.Add(NetRefHandle, InternalIndex);
// 9️⃣ 添加映射:GlobalHandle -> InternalIndex(加速公共 API 查找)
if (GlobalHandle.IsValid())
{
NetHandleToInternalIndex.Add(GlobalHandle, InternalIndex);
}
// 🔟 标记索引为已分配和可作用域
AssignedInternalIndices.SetBit(InternalIndex);
GlobalScopableInternalIndices.SetBit(InternalIndex);
// 1️⃣1️⃣ 新创建的句柄不是子对象
SubObjectInternalIndices.ClearBit(InternalIndex);
// 1️⃣2️⃣ 重置引用计数
ReplicatedObjectRefCount[InternalIndex] = 0;
return InternalIndex;
}🔗 附加实例协议源码分析
📖 源码位置: NetRefHandleManager.cpp:270-282
CPP
// NetRefHandleManager.cpp:270-282 - AttachInstanceProtocol 实现void FNetRefHandleManager::AttachInstanceProtocol(
FInternalNetRefIndex InternalIndex,
const FReplicationInstanceProtocol* InstanceProtocol,
UObject* Instance){
if (ensure((InternalIndex != InvalidInternalIndex) && InstanceProtocol))
{
FReplicatedObjectData& Data = ReplicatedObjectData[InternalIndex];
Data.InstanceProtocol = InstanceProtocol;
// 确保之前没有实例
check(ReplicatedInstances[InternalIndex] == nullptr);
ReplicatedInstances[InternalIndex] = Instance;
// 根据实例协议特性设置是否需要预发送更新
ObjectsWithPreUpdate.SetBitValue(InternalIndex,
EnumHasAnyFlags(InstanceProtocol->InstanceTraits,
EReplicationInstanceProtocolTraits::NeedsPreSendUpdate));
}
}📈 列表增长机制源码分析
当对象数量超过当前容量时,系统会自动增长列表。
📖 源码位置: NetRefHandleManager.cpp:101-127
CPP
// NetRefHandleManager.cpp:101-127 - GrowNetObjectLists 实现FInternalNetRefIndex FNetRefHandleManager::GrowNetObjectLists(){
check(AssignedInternalIndices.GetNumBits() == CurrentMaxInternalNetRefIndex);
// 旧的最大值就是下一个可用索引
const FInternalNetRefIndex NextFreeIndex = CurrentMaxInternalNetRefIndex;
// 如果已经达到最大值,返回无效索引
if (CurrentMaxInternalNetRefIndex >= MaxActiveObjectCount)
{
return InvalidInternalIndex;
}
// 增长列表
CurrentMaxInternalNetRefIndex += InternalNetRefIndexGrowSize;
if (CurrentMaxInternalNetRefIndex > MaxActiveObjectCount)
{
// 最后一次增长机会
CurrentMaxInternalNetRefIndex = MaxActiveObjectCount;
}
UE_LOG(LogIris, Log, TEXT("FNetRefHandleManager::GrowNetObjectLists grew MaxInternalIndex from %u to %u (+%u)"),
NextFreeIndex, CurrentMaxInternalNetRefIndex, CurrentMaxInternalNetRefIndex - NextFreeIndex);
// 通知所有订阅者列表已增长
MaxInternalNetRefIndexIncreased(CurrentMaxInternalNetRefIndex);
return NextFreeIndex;
}
// NetRefHandleManager.cpp:129-142 - 通知订阅者void FNetRefHandleManager::MaxInternalNetRefIndexIncreased(FInternalNetRefIndex NewMaxInternalNetRefIndex){
QUICK_SCOPE_CYCLE_COUNTER(STAT_FNetRefHandleManager_MaxInternalNetRefIndexIncreased);
CSV_CUSTOM_STAT(IrisCommon, MaxInternalIndexIncreasedCount, 1, ECsvCustomStatOp::Accumulate);
// 首先重新分配所有我们拥有的 NetBitArrays
for (FNetBitArray* NetBitArray : OwnedNetBitArrays)
{
NetBitArray->SetNumBits(NewMaxInternalNetRefIndex);
}
// 告诉其他系统也增长它们的列表
OnMaxInternalNetRefIndexIncreased.Broadcast(NewMaxInternalNetRefIndex);
}🎯 句柄与索引的关系
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🎫 句柄 vs 索引 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FNetRefHandle (句柄) │
│ │
│ • 对外使用的唯一标识符 │
│ • 包含 ReplicationSystemId(支持 PIE 多实例) │
│ • 区分静态/动态句柄 │
│ • 可以安全地在网络上传输 │
│ │
│ 结构: [ReplicationSystemId | Static/Dynamic | HandleId] │
└─────────────────────────────────────────────────────────────┘
│
│ 映射
▼
┌─────────────────────────────────────────────────────────────┐
│ FInternalNetRefIndex (内部索引) │
│ │
│ • 内部使用的数组索引 │
│ • 用于快速访问对象数据 │
│ • 不在网络上传输 │
│ • 可以被回收重用 │
│ │
│ 用途: ReplicatedObjectData[InternalIndex] │
└─────────────────────────────────────────────────────────────┘🏠 生活化类比
句柄 (Handle) 就像你的身份证号:
全国唯一
终身不变
可以在任何地方使用
内部索引 (InternalIndex) 就像你在公司的工位号:
只在公司内部有意义
你离职后,工位可以分给别人
访问速度快(直接走到那个位置)
📊 组件协作流程图
让我们看看这五个组件是如何协同工作的:
PLAINTEXT
┌─────────────────────────────────────────────────────────────────────┐
│ 🎬 Actor 开始复制的完整流程 │
└─────────────────────────────────────────────────────────────────────┘
游戏代码
│
│ Actor->SetReplicates(true)
▼
┌──────────────────────────────────────────────────────────────────┐
│ 🏭 UEngineReplicationBridge::StartReplicatingActor() │
│ │
│ 1. 验证 Actor 是否可以复制 │
│ 2. 准备复制参数(休眠状态、优先级、轮询频率) │
└────────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 🔗 UObjectReplicationBridge::StartReplicatingRootObject() │
│ │
│ 1. 获取/创建复制协议 │
│ 2. 设置轮询周期 │
│ 3. 配置过滤器和优先级器 │
└────────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 🌉 UReplicationBridge::StartReplicatingNetObject() │
│ │
│ 1. 分配网络句柄 │
│ 2. 创建网络对象 │
│ 3. 绑定实例 │
└────────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 🎫 FNetRefHandleManager::CreateNetObject() │
│ │
│ 1. 分配内部索引 │
│ 2. 分配状态缓冲区 │
│ 3. 建立句柄到索引的映射 │
│ 4. 标记为已分配 │
└────────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 🧠 UReplicationSystem (注册完成) │
│ │
│ 对象现在可以参与: │
│ • 过滤系统 - 决定谁能看到 │
│ • 优先级系统 - 决定发送顺序 │
│ • 脏数据追踪 - 检测变化 │
│ • 增量压缩 - 优化带宽 │
└──────────────────────────────────────────────────────────────────┘📁 源文件索引
组件 | 头文件 | 实现文件 | 关键行号 |
|---|---|---|---|
UReplicationSystem |
|
| Init: 106-293, PreSendUpdate: 738-838 |
UReplicationBridge |
|
| Initialize: 348-415, DestroyLocalNetHandle: 527-560 |
UObjectReplicationBridge |
|
| StartReplicatingRootObject: 421-493, StartReplicatingSubObject: 531-575 |
UEngineReplicationBridge |
|
| StartReplicatingActor: 204-381, SetNetDriver: 649-677 |
FNetRefHandleManager |
|
| Init: 31-80, InternalCreateNetObject: 188-268, GrowNetObjectLists: 101-127 |
🎯 本章小结
组件 | 一句话总结 | 关键职责 |
|---|---|---|
🧠 UReplicationSystem | 总指挥中心 | 协调所有子系统,执行主更新循环 |
🌉 UReplicationBridge | 桥接基础设施 | 对象生命周期管理,关卡组管理 |
🔗 UObjectReplicationBridge | 对象管理专家 | 休眠管理,轮询频率控制 |
🏭 UEngineReplicationBridge | UE引擎翻译官 | Actor/Component 复制,NetDriver 集成 |
🎫 FNetRefHandleManager | 身份证管理局 | 句柄分配,对象索引管理 |
💡 下一步
理解了这五大核心组件后,你可以继续学习:
📖 第五部分:过滤系统 - 了解如何决定"谁能看到什么"
📖 第六部分:优先级系统 - 了解如何决定"先发送什么"
📖 第七部分:序列化系统 - 了解数据如何被打包传输
本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)