页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        Iris 网络复制系统技术分析 - 第十部分:增量压缩

        December 28, 202530 分钟 阅读130 次阅读

        📉 Iris 网络复制系统技术分析 - 第十部分:增量压缩(Delta Compression)


        🎯 本章目标

        本章将深入剖析 Iris Delta Compression 的完整实现架构:

        • 🗄️ Baseline 存储层:DeltaCompressionBaselineStorage 的 Reserve/Commit/Cancel 延迟克隆策略与引用计数机制

        • 🎛️ Baseline 管理层:DeltaCompressionBaselineManager 的生命周期管理、共享上下文、节流策略

        • ⚠️ 失效追踪:BaselineInvalidationTracker 如何与条件复制联动

        • 🔗 序列化链路:从 ReplicationWriter → ProtocolOps → StateOps → NetSerializer 的完整调用栈

        • ✅ Ack/丢包处理:ReplicationRecord 如何驱动 baseline 状态机

        • 📊 性能模型:内存开销、CPU 开销、压缩率影响因素


        10.1 🏗️ 架构概览:Delta Compression 的分层设计

        10.1.1 📐 核心组件关系图

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────────────────┐
        │                           ReplicationWriter / ReplicationReader              │
        │  ┌─────────────────────────────────────────────────────────────────────────┐│
        │  │ SerializeObjectStateDelta() / DeserializeObjectStateDelta()             ││
        │  │   - 写/读 BaselineIndex (2 bits)                                        ││
        │  │   - 写/读 NewBaseline 标志                                               ││
        │  │   - 调用 ProtocolOps::SerializeWithMaskDelta / DeserializeWithMaskDelta ││
        │  └─────────────────────────────────────────────────────────────────────────┘│
        └─────────────────────────────────────────────────────────────────────────────┘
                                              │
                                              ▼
        ┌─────────────────────────────────────────────────────────────────────────────┐
        │                         ReplicationProtocolOperations                        │
        │  ┌─────────────────────────────────────────────────────────────────────────┐│
        │  │ SerializeWithMaskDelta(ChangeMask, SrcState, PrevState, Protocol)       ││
        │  │   - 写 ChangeMask (SparseBitArray)                                      ││
        │  │   - 遍历 ReplicationState,调用 StateOps::SerializeDeltaWithMask        ││
        │  └─────────────────────────────────────────────────────────────────────────┘│
        └─────────────────────────────────────────────────────────────────────────────┘
                                              │
                                              ▼
        ┌─────────────────────────────────────────────────────────────────────────────┐
        │                         ReplicationStateOperations                           │
        │  ┌─────────────────────────────────────────────────────────────────────────┐│
        │  │ SerializeDeltaWithMask(ChangeMask, Src, Prev, Descriptor)               ││
        │  │   - 遍历 Member,只序列化 ChangeMask 标记为脏的成员                      ││
        │  │   - 调用 NetSerializer::SerializeDelta(Source, Prev)                    ││
        │  └─────────────────────────────────────────────────────────────────────────┘│
        └─────────────────────────────────────────────────────────────────────────────┘
                                              │
                                              ▼
        ┌─────────────────────────────────────────────────────────────────────────────┐
        │                              NetSerializer                                   │
        │  ┌─────────────────────────────────────────────────────────────────────────┐│
        │  │ SerializeDelta(Context, {Source, Prev, ChangeMaskInfo})                 ││
        │  │   - 自定义 delta 逻辑 或 默认 delta (IsEqual ? skip : Serialize)        ││
        │  └─────────────────────────────────────────────────────────────────────────┘│
        └─────────────────────────────────────────────────────────────────────────────┘

        10.1.2 🔄 Baseline 管理组件关系

        PLAINTEXT
        ┌──────────────────────────────────────────────────────────────────────┐
        │                    DeltaCompressionBaselineManager                    │
        │  - 对象级 Delta 启用状态 (DeltaCompressionEnabledObjects BitArray)    │
        │  - 连接级 Baseline 信息 (ObjectInfo->BaselinesForConnections)         │
        │  - Baseline 创建节流 (MinimumNumberOfFramesBetweenBaselines)          │
        │  - 同帧共享上下文 (BaselineSharingContext)                            │
        └──────────────────────────────────────────────────────────────────────┘
                                   │ 使用
                                   ▼
        ┌──────────────────────────────────────────────────────────────────────┐
        │                   DeltaCompressionBaselineStorage                     │
        │  - StateBuffer 分配/释放 (通过 ReplicationStateStorage)               │
        │  - 引用计数管理 (RefCount)                                            │
        │  - Reserve/Commit/Cancel 延迟克隆                                     │
        └──────────────────────────────────────────────────────────────────────┘
                                   │ 使用
                                   ▼
        ┌──────────────────────────────────────────────────────────────────────┐
        │                      ReplicationStateStorage                          │
        │  - 实际内存分配 (FMemory::Malloc)                                     │
        │  - 状态克隆 (CloneState)                                              │
        │  - Baseline 预留/提交/取消                                            │
        └──────────────────────────────────────────────────────────────────────┘

        10.2 🗄️ Baseline 存储层:DeltaCompressionBaselineStorage 深度剖析

        10.2.1 📦 核心数据结构

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineStorage.h
        
        // 对外暴露的 Baseline 状态信息class FDeltaCompressionBaselineStateInfo
        {
        public:
            bool IsValid() const { return StateBuffer != nullptr; }
            
            uint8* StateBuffer = nullptr;  // 指向量化后的对象状态
            DeltaCompressionBaselineStateInfoIndexType StateInfoIndex = InvalidDeltaCompressionBaselineStateInfoIndex;
        };
        
        // 内部存储结构(带引用计数)struct FInternalBaselineStateInfo
        {
            uint8* StateBuffer = nullptr;
            uint32 ObjectIndex = 0;
            uint32 RefCount = 1;  // 关键:支持多连接共享同一 baseline
        };

        设计要点:

        • 💾 StateBuffer 存储的是量化后的内部状态(Internal State),不是原始 UObject 属性

        • 🔢 RefCount 实现多连接共享:同一帧内多个连接可能使用同一个 baseline,避免重复克隆

        • 🏷️ StateInfoIndex 是 baseline 在存储数组中的索引,用于快速查找

        10.2.2 ⏳ Reserve/Commit/Cancel:延迟克隆策略

        这是 Iris 的一个精妙设计——不在创建 baseline 时立即克隆状态,而是延迟到确认需要时才克隆。

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineStorage.cpp
        
        // 阶段1:Reserve - 预留但不克隆FDeltaCompressionBaselineStateInfo FDeltaCompressionBaselineStorage::ReserveBaselineForCurrentState(uint32 ObjectIndex){
            FDeltaCompressionBaselineStateInfo BaselineStateInfo;
            
            // 分配内部索引
            const uint32 StateInfoIndex = AllocBaselineStateInfo();
            FInternalBaselineStateInfo* InternalInfo = &BaselineStateInfos[StateInfoIndex];
            InternalInfo->ObjectIndex = ObjectIndex;
            InternalInfo->RefCount = 1;
            
            // 关键:从 ReplicationStateStorage 获取"预留"
            // 此时 StateBuffer 指向当前发送状态,尚未克隆
            FReplicationStateStorage::FBaselineReservation Reservation = 
                ReplicationStateStorage->ReserveBaseline(ObjectIndex, EReplicationStateType::CurrentSendState);
            
            InternalInfo->StateBuffer = const_cast<uint8*>(Reservation.BaselineBaseStorage);
            BaselineStateInfo.StateBuffer = InternalInfo->StateBuffer;
            BaselineStateInfo.StateInfoIndex = StateInfoIndex;
            
            return BaselineStateInfo;
        }
        
        // 阶段2:根据引用计数决定 Commit 还是 Cancelvoid FDeltaCompressionBaselineStorage::OptionallyCommitAndDoReleaseBaseline(
            DeltaCompressionBaselineStateInfoIndexType StateInfoIndex){
            FInternalBaselineStateInfo* InternalInfo = &BaselineStateInfos[StateInfoIndex];
            
            if (--InternalInfo->RefCount == 0)
            {
                // 没有连接需要这个 baseline 了,取消预留(不克隆)
                ReplicationStateStorage->CancelBaselineReservation(
                    InternalInfo->ObjectIndex, InternalInfo->StateBuffer);
                FreeBaselineStateInfo(StateInfoIndex);
            }
            else
            {
                // 还有连接需要,提交预留(执行克隆)
                ReplicationStateStorage->CommitBaselineReservation(
                    InternalInfo->ObjectIndex, 
                    InternalInfo->StateBuffer, 
                    EReplicationStateType::CurrentSendState);
            }
        }

        为什么要这样设计? 🤔

        考虑这个场景:服务器有 100 个连接,某对象需要创建 baseline。

        • ❌ 朴素方案:立即克隆 100 份 → 100 次内存分配 + 100 次 memcpy

        • ✅ Iris 方案:

          1. Reserve 时只记录指针(指向当前状态)

          2. 同帧内其他连接共享同一个 Reserve(RefCount++)

          3. 帧末统一 Commit(只克隆 1 次)或 Cancel(0 次克隆)

        10.2.3 💽 底层内存分配:ReplicationStateStorage

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationState/ReplicationStateStorage.cpp
        
        uint8* FReplicationStateStorage::AllocBaseline(uint32 ObjectIndex, EReplicationStateType Base){
            LLM_SCOPE_BYTAG(IrisState);
            
            const FObjectInfo& ObjectInfo = ObjectInfos[ObjectIndex];
            const FReplicationProtocol* Protocol = ObjectInfo.Protocol;
            
            // 按协议定义的大小和对齐分配
            uint8* Storage = static_cast<uint8*>(
                FMemory::Malloc(Protocol->InternalTotalSize, Protocol->InternalTotalAlignment));
            
            // 根据 Base 类型初始化
            switch (Base)
            {
            case EReplicationStateType::UninitializedState:
                // 不初始化,调用方负责填充
                break;
                
            case EReplicationStateType::ZeroedState:
                FMemory::Memzero(Storage, Protocol->InternalTotalSize);
                break;
                
            case EReplicationStateType::DefaultState:
                // 从 CDO 的量化状态克隆
                CloneState(Protocol, Storage, Protocol->DefaultStateBuffer);
                break;
                
            case EReplicationStateType::CurrentSendState:
                // 从当前发送状态克隆
                CloneState(Protocol, Storage, ObjectInfo.StateBuffers[SendStateBufferIndex]);
                break;
                
            case EReplicationStateType::CurrentRecvState:
                // 从当前接收状态克隆
                CloneState(Protocol, Storage, ObjectInfo.StateBuffers[RecvStateBufferIndex]);
                break;
            }
            
            return Storage;
        }

        内存布局 📐:

        • 每个 baseline 占用 Protocol->InternalTotalSize 字节

        • 对齐要求:Protocol->InternalTotalAlignment

        • 内容:所有 ReplicationState 的量化成员按顺序排列


        10.3 🎛️ Baseline 管理层:DeltaCompressionBaselineManager 深度剖析

        10.3.1 🔒 核心设计约束

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineManager.h
        
        class FDeltaCompressionBaselineManager
        {
        public:
            // 硬编码约束:每个对象最多 2 个 baseline
            enum : uint32 { MaxBaselineCount = 2 };
            
            // InvalidBaselineIndex = 2,用 2 bits 编码 {0, 1, Invalid}
            enum : uint32 { InvalidBaselineIndex = MaxBaselineCount };
            enum : uint32 { BaselineIndexBitCount = 2 };
            
            // ...
        };

        为什么只有 2 个 baseline? 🤔

        这是一个空间-时间权衡 ⚖️:

        • 2 个 baseline 足以支持"滑动窗口"式的 Ack:发送 baseline[0],等 Ack;发送 baseline[1],等 Ack;Ack[0] 到了,释放 baseline[0],可以创建新的 baseline[0]...

        • 更多 baseline 会增加内存开销,且实际收益有限(网络延迟通常不会导致需要 >2 个未确认 baseline)

        10.3.2 📋 对象级数据结构

        CPP
        // DeltaCompressionBaselineManager.h
        
        // 每个启用 Delta 压缩的对象的信息struct FObjectInfo
        {
            // 每个连接的 baseline 信息
            TArray<FObjectBaselineInfo> BaselinesForConnections;
            
            // 每个连接的 ChangeMask(自上次 baseline 以来的脏字段)
            ChangeMaskStorageType* ChangeMasksForConnections = nullptr;
            uint32 ChangeMaskStride = 0;  // 每个连接的 ChangeMask 字数
            
            // 节流控制
            uint32 PrevBaselineCreationFrame = 0;  // 上次创建 baseline 的帧号
        };
        
        // 每个连接的 baseline 信息struct FObjectBaselineInfo
        {
            FInternalBaseline Baselines[MaxBaselineCount];  // 最多 2 个 baseline
        };
        
        // 单个 baseline 的内部表示struct FInternalBaseline
        {
            bool IsValid() const { return BaselineStateInfoIndex != InvalidIndex; }
            
            DeltaCompressionBaselineStateInfoIndexType BaselineStateInfoIndex = InvalidIndex;
            ChangeMaskStorageType* ChangeMask = nullptr;  // 该 baseline 创建时的 ChangeMask 快照
        };

        10.3.3 🔧 CreateBaseline:完整流程

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineManager.cpp
        
        FDeltaCompressionBaseline FDeltaCompressionBaselineManager::CreateBaseline(
            uint32 ConnId, uint32 ObjectIndex, uint32 BaselineIndex){
            FDeltaCompressionBaseline Baseline;
            
            // 1. 检查对象是否启用 Delta 压缩
            if (!DeltaCompressionEnabledObjects.GetBit(ObjectIndex))
            {
                return Baseline;  // 返回无效 baseline
            }
            
            FObjectInfo* ObjectInfo = GetObjectInfo(ObjectIndex);
            const uint32 ObjectInfoIndex = GetObjectInfoIndex(ObjectIndex);
            
            FDeltaCompressionBaselineStateInfo BaselineStateInfo;
            
            // 2. 检查是否可以共享已创建的 baseline(同帧优化)
            if (BaselineSharingContext.ObjectInfoIndicesWithNewBaseline.GetBit(ObjectInfoIndex))
            {
                // 本帧已经为这个对象创建过 baseline,复用它
                const auto ExistingStateInfoIndex = 
                    BaselineSharingContext.ObjectInfoIndexToBaselineInfoIndex[ObjectInfoIndex];
                BaselineStateInfo = BaselineStorage.GetBaselineReservationForCurrentState(ExistingStateInfoIndex);
            }
            else if (IsAllowedToCreateBaselineForObject(ConnId, ObjectIndex, ObjectInfo, ObjectInfoIndex))
            {
                // 3. 允许创建新 baseline
                BaselineStateInfo = BaselineStorage.ReserveBaselineForCurrentState(ObjectIndex);
                
                // 记录到共享上下文,供同帧其他连接复用
                BaselineSharingContext.ObjectInfoIndicesWithNewBaseline.SetBit(ObjectInfoIndex);
                BaselineSharingContext.ObjectInfoIndexToBaselineInfoIndex[ObjectInfoIndex] = 
                    BaselineStateInfo.StateInfoIndex;
                ++BaselineSharingContext.CreatedBaselineCount;
                
                // 更新节流计数器
                ObjectInfo->PrevBaselineCreationFrame = FrameCounter;
            }
            else
            {
                // 4. 不允许创建(节流限制)
                return Baseline;
            }
            
            // 5. 增加引用计数(该连接持有这个 baseline)
            BaselineStorage.AddRefBaseline(BaselineStateInfo.StateInfoIndex);
            
            // 6. 设置内部 baseline 信息
            FObjectBaselineInfo& BaselineInfo = ObjectInfo->BaselinesForConnections[ConnId];
            FInternalBaseline& InternalBaseline = BaselineInfo.Baselines[BaselineIndex];
            InternalBaseline.BaselineStateInfoIndex = BaselineStateInfo.StateInfoIndex;
            
            // 7. 关键:复制连接特定的 ChangeMask 到 baseline,并清零连接 ChangeMask
            {
                const uint32 ChangeMaskStride = ObjectInfo->ChangeMaskStride;
                const SIZE_T ChangeMaskOffset = ChangeMaskStride * ConnId;
                ChangeMaskStorageType* ConnectionChangeMask = 
                    ObjectInfo->ChangeMasksForConnections + ChangeMaskOffset;
                
                for (uint32 WordIt = 0; WordIt < ChangeMaskStride; ++WordIt)
                {
                    InternalBaseline.ChangeMask[WordIt] = ConnectionChangeMask[WordIt];
                    ConnectionChangeMask[WordIt] = 0;  // 清零,开始记录下一个周期的变化
                }
            }
            
            // 8. 返回可用于序列化的 baseline
            Baseline.ChangeMask = InternalBaseline.ChangeMask;
            Baseline.StateBuffer = BaselineStateInfo.StateBuffer;
            return Baseline;
        }

        10.3.4 🚦 Baseline 创建节流策略

        CPP
        // DeltaCompressionBaselineManager.cpp
        
        // CVar 控制static int32 MinimumNumberOfFramesBetweenBaselines = 60;
        static FAutoConsoleVariableRef CVarMinimumNumberOfFramesBetweenBaselines(
            TEXT("net.Iris.MinimumNumberOfFramesBetweenBaselines"),
            MinimumNumberOfFramesBetweenBaselines,
            TEXT("Minimum number of frames between creation of new delta compression baselines for an object. Default is 60.")
        );
        
        bool FDeltaCompressionBaselineManager::IsAllowedToCreateBaselineForObject(
            uint32 ConnId, uint32 ObjectIndex, 
            const FObjectInfo* ObjectInfo, uint32 ObjectInfoIndex) const{
            // 规则1:如果该连接没有任何 baseline,允许创建
            const uint8 BaselineCount = BaselineCounts[ObjectInfoIndex * MaxConnectionCount + ConnId];
            if (BaselineCount == 0)
            {
                return true;
            }
            
            // 规则2:检查帧间隔
            const uint32 FramesSincePrevBaselineCreation = 
                FrameCounter - ObjectInfo->PrevBaselineCreationFrame;
            
            if (FramesSincePrevBaselineCreation >= static_cast<uint32>(MinimumNumberOfFramesBetweenBaselines))
            {
                return true;
            }
            
            // 规则3:如果本帧已经为该对象创建过 baseline(被其他连接触发),允许共享
            if (BaselineSharingContext.ObjectInfoIndicesWithNewBaseline.GetBit(ObjectInfoIndex))
            {
                return true;
            }
            
            return false;
        }

        节流策略的意义 💡:

        • ⏱️ 默认 60 帧(约 1 秒 @60fps)才能创建新 baseline

        • 🛡️ 防止频繁创建 baseline 导致的 CPU/内存开销

        • 🤝 同帧共享机制确保多连接场景不会重复创建

        10.3.5 🤝 同帧共享上下文

        CPP
        // DeltaCompressionBaselineManager.h
        
        struct FBaselineSharingContext
        {
            // 标记哪些对象在本帧已创建 baseline
            FNetBitArray ObjectInfoIndicesWithNewBaseline;
            
            // 对象索引 → baseline StateInfoIndex 的映射
            TArray<DeltaCompressionBaselineStateInfoIndexType> ObjectInfoIndexToBaselineInfoIndex;
            
            // 统计:本帧创建的 baseline 数量
            uint32 CreatedBaselineCount = 0;
        };

        工作流程 🔄:

        1. 帧开始时,BeginBaselineSharingContext() 清空上下文

        2. 第一个连接请求创建 baseline → Reserve + 记录到上下文

        3. 后续连接请求同一对象的 baseline → 直接从上下文获取,AddRef

        4. 帧结束时,EndBaselineSharingContext() 执行 Commit/Cancel


        10.4 ⚠️ Baseline 失效追踪:DeltaCompressionBaselineInvalidationTracker

        10.4.1 ❓ 为什么需要失效追踪?

        条件复制(Conditionals)是 baseline 失效的主要来源:

        PLAINTEXT
        场景:某字段 Health 配置为 COND_OwnerOnly 🏥
        
        时刻 T1:PlayerA 是 Owner,baseline 包含 Health=100
        时刻 T2:Owner 变更为 PlayerB
                 ↓
        问题 💥:PlayerA 的 baseline 中仍有 Health=100,但 PlayerA 现在不应该看到 Health
        如果继续用这个 baseline 计算 delta,会导致状态错乱

        10.4.2 🔍 失效追踪器实现

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineInvalidationTracker.h
        
        class FDeltaCompressionBaselineInvalidationTracker
        {
        public:
            enum Constants : uint32
            {
                // 特殊值:使所有连接的 baseline 失效
                InvalidateBaselineForAllConnections = 0U,
            };
        
            struct FInvalidationInfo
            {
                uint32 ConnId = InvalidateBaselineForAllConnections;
                FInternalNetRefIndex ObjectIndex = 0U;
            };
        
            // 标记某对象的 baseline 需要失效
            void InvalidateBaselines(FInternalNetRefIndex ObjectIndex, uint32 ConnId);
            
            // 获取待处理的失效列表
            TArrayView<const FInvalidationInfo> GetBaselineInvalidationInfos() const;
            
        private:
            FNetBitArray InvalidatedObjects;  // 避免重复添加
            TArray<FInvalidationInfo> InvalidationInfos;
        };
        
        // DeltaCompressionBaselineInvalidationTracker.cpp
        
        void FDeltaCompressionBaselineInvalidationTracker::InvalidateBaselines(
            FInternalNetRefIndex ObjectIndex, uint32 ConnId){
            // 避免重复处理
            if (InvalidatedObjects.GetBit(ObjectIndex))
            {
                return;
            }
            
            // 检查对象是否启用 Delta 压缩
            if (BaselineManager->GetDeltaCompressionStatus(ObjectIndex) != ENetObjectDeltaCompressionStatus::Allow)
            {
                return;
            }
            
            if (ConnId == InvalidateBaselineForAllConnections)
            {
                InvalidatedObjects.SetBit(ObjectIndex);
            }
            
            InvalidationInfos.Emplace(FInvalidationInfo{ConnId, ObjectIndex});
        }

        10.4.3 🔗 条件复制触发失效

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/Conditionals/ReplicationConditionals.cpp
        
        void FReplicationConditionals::InvalidateBaselinesForObjectHierarchy(
            uint32 ObjectIndex, 
            const TConstArrayView<uint32>& ConnectionsToInvalidate){
            // 1. 处理根对象
            {
                const FNetRefHandleManager::FReplicatedObjectData& ObjectData = 
                    NetRefHandleManager->GetReplicatedObjectDataNoCheck(ObjectIndex);
                
                // 只有带生命周期条件的对象才需要失效
                if (EnumHasAnyFlags(ObjectData.Protocol->ProtocolTraits, 
                    EReplicationProtocolTraits::HasLifetimeConditionals))
                {
                    for (const uint32 ConnId : ConnectionsToInvalidate)
                    {
                        BaselineInvalidationTracker->InvalidateBaselines(ObjectIndex, ConnId);
                    }
                }
            }
        
            // 2. 递归处理子对象
            for (const FInternalNetRefIndex SubObjectIndex : NetRefHandleManager->GetSubObjects(ObjectIndex))
            {
                const FNetRefHandleManager::FReplicatedObjectData& SubObjectData = 
                    NetRefHandleManager->GetReplicatedObjectDataNoCheck(SubObjectIndex);
                
                if (EnumHasAnyFlags(SubObjectData.Protocol->ProtocolTraits, 
                    EReplicationProtocolTraits::HasLifetimeConditionals))
                {
                    for (const uint32 ConnId : ConnectionsToInvalidate)
                    {
                        BaselineInvalidationTracker->InvalidateBaselines(SubObjectIndex, ConnId);
                    }
                }
            }
        }

        10.4.4 🧹 失效处理

        CPP
        // DeltaCompressionBaselineManager.cpp
        
        void FDeltaCompressionBaselineManager::InvalidateBaselinesDueToModifiedConditionals(){
            for (const FInvalidationInfo& Info : BaselineInvalidationTracker->GetBaselineInvalidationInfos())
            {
                FObjectInfo* ObjectInfo = GetObjectInfo(Info.ObjectIndex);
                
                if (Info.ConnId == InvalidateBaselineForAllConnections)
                {
                    // 使所有连接的 baseline 失效
                    for (uint32 ConnectionId : ValidConnections)
                    {
                        InvalidateBaselinesForConnection(ObjectInfo, ConnectionId);
                    }
                }
                else
                {
                    // 只使特定连接的 baseline 失效
                    InvalidateBaselinesForConnection(ObjectInfo, Info.ConnId);
                }
            }
            
            BaselineInvalidationTracker->Reset();
        }
        
        void FDeltaCompressionBaselineManager::InvalidateBaselinesForConnection(
            FObjectInfo* ObjectInfo, uint32 ConnId){
            FObjectBaselineInfo& BaselineInfo = ObjectInfo->BaselinesForConnections[ConnId];
            
            for (uint32 BaselineIndex = 0; BaselineIndex < MaxBaselineCount; ++BaselineIndex)
            {
                FInternalBaseline& InternalBaseline = BaselineInfo.Baselines[BaselineIndex];
                
                if (InternalBaseline.IsValid())
                {
                    // 释放 baseline,但保留 ChangeMask(合并回连接 ChangeMask)
                    ReleaseInternalBaseline(InternalBaseline, EChangeMaskBehavior::Merge);
                }
            }
        }

        10.5 🔗 序列化链路:从 Writer 到 NetSerializer 的完整调用栈

        10.5.1 📤 发送侧:ReplicationWriter::SerializeObjectStateDelta

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationWriter.cpp
        
        void FReplicationWriter::SerializeObjectStateDelta(
            FNetSerializationContext& Context,
            const FNetRefHandleManager::FReplicatedObjectData& ObjectData,
            FReplicationInfo& Info,
            FDeltaCompressionBaseline& CurrentBaseline,
            uint32 CreatedBaselineIndex){
            FNetBitStreamWriter& Writer = *Context.GetBitStreamWriter();
            
            // 1. 写 BaselineIndex (2 bits)
            // 告诉客户端:我基于哪个 baseline 计算的 delta
            Writer.WriteBits(Info.LastAckedBaselineIndex, FDeltaCompressionBaselineManager::BaselineIndexBitCount);
            
            if (Info.LastAckedBaselineIndex != FDeltaCompressionBaselineManager::InvalidBaselineIndex)
            {
                // 2a. 有有效 baseline,走 delta 路径
                
                // 写 NewBaseline 标志:本包是否携带新 baseline
                const bool bHasNewBaseline = (CreatedBaselineIndex != FDeltaCompressionBaselineManager::InvalidBaselineIndex);
                Writer.WriteBool(bHasNewBaseline);
                
                // 调用协议级 delta 序列化
                FReplicationProtocolOperations::SerializeWithMaskDelta(
                    Context,
                    Info.GetChangeMaskStoragePointer(),
                    ReplicatedObjectStateBuffer,      // 当前状态
                    CurrentBaseline.StateBuffer,       // baseline 状态
                    ObjectData.Protocol);
            }
            else
            {
                // 2b. 无有效 baseline,走全量路径
                
                // 写 NewBaselineIndex:告诉客户端存储这个作为新 baseline
                Writer.WriteBits(CreatedBaselineIndex, FDeltaCompressionBaselineManager::BaselineIndexBitCount);
                
                // 调用普通序列化(非 delta)
                FReplicationProtocolOperations::SerializeWithMask(
                    Context,
                    Info.GetChangeMaskStoragePointer(),
                    ReplicatedObjectStateBuffer,
                    ObjectData.Protocol);
            }
        }

        10.5.2 📜 协议级:SerializeWithMaskDelta

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationOperations.cpp
        
        void FReplicationProtocolOperations::SerializeWithMaskDelta(
            FNetSerializationContext& Context,
            const uint32* ChangeMaskData,
            const uint8* SrcObjectStateBuffer,
            const uint8* PrevObjectStateBuffer,
            const FReplicationProtocol* Protocol){
            FNetBitStreamWriter& Writer = *Context.GetBitStreamWriter();
            
            // 1. 写 ChangeMask(使用稀疏位数组格式压缩)
            WriteSparseBitArray(&Writer, ChangeMaskData, Protocol->ChangeMaskBitCount);
            
            // 2. 准备 ChangeMask 视图
            const FNetBitArrayView ChangeMask = MakeNetBitArrayView(ChangeMaskData, Protocol->ChangeMaskBitCount);
            
            // 3. 遍历所有 ReplicationState
            const FReplicationStateDescriptor* const* ReplicationStateDescriptors = Protocol->ReplicationStateDescriptors;
            uint32 CurrentChangeMaskBitOffset = 0;
            
            for (uint32 StateIt = 0; StateIt < Protocol->ReplicationStateCount; ++StateIt)
            {
                const FReplicationStateDescriptor* CurrentDescriptor = ReplicationStateDescriptors[StateIt];
                
                // 计算当前状态在 buffer 中的偏移
                const uint8* CurrentInternalStateBuffer = SrcObjectStateBuffer + CurrentDescriptor->ExternalOffset;
                const uint8* PrevInternalStateBuffer = PrevObjectStateBuffer + CurrentDescriptor->ExternalOffset;
                
                if (CurrentDescriptor->IsInitState())
                {
                    // InitState:完整 delta 序列化(不使用 ChangeMask)
                    FReplicationStateOperations::SerializeDelta(
                        Context, 
                        CurrentInternalStateBuffer, 
                        PrevInternalStateBuffer, 
                        CurrentDescriptor);
                }
                else
                {
                    // 普通 State:带 Mask 的 delta 序列化
                    FReplicationStateOperations::SerializeDeltaWithMask(
                        Context,
                        ChangeMask,
                        CurrentChangeMaskBitOffset,
                        CurrentInternalStateBuffer,
                        PrevInternalStateBuffer,
                        CurrentDescriptor);
                }
                
                CurrentChangeMaskBitOffset += CurrentDescriptor->ChangeMaskBitCount;
            }
        }

        10.5.3 📊 状态级:SerializeDeltaWithMask

        CPP
        // ReplicationOperations.cpp
        
        void FReplicationStateOperations::SerializeDeltaWithMask(
            FNetSerializationContext& Context,
            const FNetBitArrayView& ChangeMask,
            const uint32 ChangeMaskOffset,
            const uint8* RESTRICT SrcInternalBuffer,
            const uint8* RESTRICT PrevInternalBuffer,
            const FReplicationStateDescriptor* Descriptor){
            const FReplicationStateMemberDescriptor* MemberDescriptors = Descriptor->MemberDescriptors;
            const FReplicationStateMemberSerializerDescriptor* MemberSerializerDescriptors = 
                Descriptor->MemberSerializerDescriptors;
            const FReplicationStateMemberChangeMaskDescriptor* MemberChangeMaskDescriptors = 
                Descriptor->MemberChangeMaskDescriptors;
            const uint32 MemberCount = Descriptor->MemberCount;
            
            for (uint32 MemberIt = 0; MemberIt < MemberCount; ++MemberIt)
            {
                const FReplicationStateMemberDescriptor& MemberDescriptor = MemberDescriptors[MemberIt];
                const FReplicationStateMemberSerializerDescriptor& MemberSerializerDescriptor = 
                    MemberSerializerDescriptors[MemberIt];
                const FReplicationStateMemberChangeMaskDescriptor& MemberChangeMaskDescriptor = 
                    MemberChangeMaskDescriptors[MemberIt];
                
                // 计算该成员在 ChangeMask 中的位置
                const uint32 MemberChangeMaskOffset = ChangeMaskOffset + MemberChangeMaskDescriptor.BitOffset;
                
                // 关键:只序列化 ChangeMask 中标记为脏的成员
                if (ChangeMask.IsAnyBitSet(MemberChangeMaskOffset, MemberChangeMaskDescriptor.BitCount))
                {
                    FNetSerializeDeltaArgs Args;
                    Args.Version = 0;
                    Args.NetSerializerConfig = MemberSerializerDescriptor.SerializerConfig;
                    Args.Source = NetSerializerValuePointer(SrcInternalBuffer + MemberDescriptor.InternalMemberOffset);
                    Args.Prev = NetSerializerValuePointer(PrevInternalBuffer + MemberDescriptor.InternalMemberOffset);
                    Args.ChangeMaskInfo.BitCount = MemberChangeMaskDescriptor.BitCount;
                    Args.ChangeMaskInfo.BitOffset = MemberChangeMaskOffset;
                    
                    // 调用 NetSerializer 的 SerializeDelta
                    MemberSerializerDescriptor.Serializer->SerializeDelta(Context, Args);
                }
            }
        }

        10.5.4 🎯 字段级:NetSerializer::SerializeDelta

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Serialization/NetSerializer.h
        
        struct FNetSerializeDeltaArgs : FNetSerializeArgs
        {
            // 指向上一个已确认的量化数据(baseline)
            NetSerializerValuePointer Prev;
        };
        
        // 典型的自定义 delta 实现(以 QuantizedVector 为例)static void SerializeDelta(FNetSerializationContext& Context, const FNetSerializeDeltaArgs& Args){
            const QuantizedType& Value = *reinterpret_cast<const QuantizedType*>(Args.Source);
            const QuantizedType& PrevValue = *reinterpret_cast<const QuantizedType*>(Args.Prev);
            
            FNetBitStreamWriter* Writer = Context.GetBitStreamWriter();
            
            // 计算差值
            const int32 DeltaX = Value.X - PrevValue.X;
            const int32 DeltaY = Value.Y - PrevValue.Y;
            const int32 DeltaZ = Value.Z - PrevValue.Z;
            
            // 写差值(通常用更少的位数)
            Writer->WriteBits(DeltaX, DeltaBitCount);
            Writer->WriteBits(DeltaY, DeltaBitCount);
            Writer->WriteBits(DeltaZ, DeltaBitCount);
        }

        10.5.5 🔄 默认 Delta 实现

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Serialization/NetSerializerBuilder.inl
        
        // 如果 Serializer 没有实现 SerializeDelta,且满足条件,使用默认实现template<typename T = void, 
                 typename U = typename TEnableIf<!HasSerializeDelta && ShouldUseDefaultDelta() && HasIsEqual, T>::Type, 
                 char V = 0>
        static NetSerializeDeltaFunction GetSerializeDeltaFunction() 
        { 
            return NetSerializeDeltaDefault<NetSerializerImpl::Serialize, NetSerializerImpl::IsEqual>; 
        }
        
        // 默认 delta 实现template<NetSerializeFunction SerializeFunc, NetIsEqualFunction IsEqualFunc>
        void NetSerializeDeltaDefault(FNetSerializationContext& Context, const FNetSerializeDeltaArgs& Args){
            // 如果值相等,写 1 bit (0) 表示"无变化"
            // 如果值不等,写 1 bit (1) + 完整序列化
            
            FNetIsEqualArgs IsEqualArgs;
            IsEqualArgs.Source0 = Args.Source;
            IsEqualArgs.Source1 = Args.Prev;
            IsEqualArgs.NetSerializerConfig = Args.NetSerializerConfig;
            
            const bool bIsEqual = IsEqualFunc(Context, IsEqualArgs);
            
            FNetBitStreamWriter* Writer = Context.GetBitStreamWriter();
            Writer->WriteBool(!bIsEqual);
            
            if (!bIsEqual)
            {
                // 值不同,退化为完整序列化
                FNetSerializeArgs SerializeArgs;
                SerializeArgs.Source = Args.Source;
                SerializeArgs.NetSerializerConfig = Args.NetSerializerConfig;
                SerializeFunc(Context, SerializeArgs);
            }
        }

        默认 delta 的局限性 ⚠️:

        • ❌ 只能做"相等则跳过,不等则全量"

        • ❌ 无法利用数值接近性(如位置小幅变化)

        • ❌ 对于复杂类型(数组、结构体),可能不如自定义 delta 高效


        10.6 📥 接收侧:ReplicationReader 的 Delta 反序列化

        10.6.1 🔍 DeserializeObjectStateDelta

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationReader.cpp
        
        void FReplicationReader::DeserializeObjectStateDelta(
            FNetSerializationContext& Context,
            const FNetRefHandleManager::FReplicatedObjectData& ObjectData,
            FReplicationInfo& Info,
            FObjectReferenceCache& ObjectReferenceCache,
            uint32& OutNewBaselineIndex){
            FNetBitStreamReader& Reader = *Context.GetBitStreamReader();
            
            // 1. 读 BaselineIndex
            const uint32 BaselineIndex = Reader.ReadBits(FDeltaCompressionBaselineManager::BaselineIndexBitCount);
            
            if (BaselineIndex != FDeltaCompressionBaselineManager::InvalidBaselineIndex)
            {
                // 2a. 有效 baseline,走 delta 路径
                
                // 读 NewBaseline 标志
                const bool bIsNewBaseline = Reader.ReadBool();
                OutNewBaselineIndex = bIsNewBaseline ? BaselineIndex : InvalidBaselineIndex;
                
                // 获取本地存储的 baseline
                const uint8* StoredBaseline = ObjectInfo.StoredBaselines[BaselineIndex];
                
                // 调用协议级 delta 反序列化
                FReplicationProtocolOperations::DeserializeWithMaskDelta(
                    Context,
                    Info.ChangeMaskOrPointer.GetPointer(ObjectInfo.ChangeMaskBitCount),
                    ObjectData.ReceiveStateBuffer,  // 目标:接收状态 buffer
                    StoredBaseline,                  // 参照:存储的 baseline
                    ObjectData.Protocol);
            }
            else
            {
                // 2b. 无效 baseline,走全量路径
                
                // 读 NewBaselineIndex
                OutNewBaselineIndex = Reader.ReadBits(FDeltaCompressionBaselineManager::BaselineIndexBitCount);
                
                // 调用普通反序列化
                FReplicationProtocolOperations::DeserializeWithMask(
                    Context,
                    Info.ChangeMaskOrPointer.GetPointer(ObjectInfo.ChangeMaskBitCount),
                    ObjectData.ReceiveStateBuffer,
                    ObjectData.Protocol);
            }
        }

        10.6.2 💾 客户端 Baseline 存储

        CPP
        // ReplicationReader.h
        
        struct FObjectInfo
        {
            // 存储的 baseline(最多 2 个)
            uint8* StoredBaselines[FDeltaCompressionBaselineManager::MaxBaselineCount] = {nullptr, nullptr};
            
            // ...
        };
        
        // 收到新 baseline 后存储void FReplicationReader::StoreBaseline(FObjectInfo& ObjectInfo, uint32 BaselineIndex, const uint8* StateBuffer){
            // 如果已有旧 baseline,释放
            if (ObjectInfo.StoredBaselines[BaselineIndex] != nullptr)
            {
                FMemory::Free(ObjectInfo.StoredBaselines[BaselineIndex]);
            }
            
            // 分配新存储并克隆
            const uint32 StateSize = ObjectInfo.Protocol->InternalTotalSize;
            ObjectInfo.StoredBaselines[BaselineIndex] = static_cast<uint8*>(
                FMemory::Malloc(StateSize, ObjectInfo.Protocol->InternalTotalAlignment));
            FMemory::Memcpy(ObjectInfo.StoredBaselines[BaselineIndex], StateBuffer, StateSize);
        }

        10.7 ✅ Ack 与丢包处理:Baseline 状态机

        10.7.1 📝 ReplicationRecord:记录发送历史

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationRecord.h
        
        struct FReplicationRecord
        {
            struct FRecordInfo
            {
                // 本次发送使用的 baseline index
                uint32 BaselineIndex : 2;
                
                // 本次发送创建的新 baseline index(如果有)
                uint32 NewBaselineIndex : 2;
                
                // 其他信息...
            };
            
            // 每个对象的发送记录
            TArray<FRecordInfo> RecordInfos;
        };

        10.7.2 ✅ Ack 处理:HandleDeliveredRecord

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationWriter.cpp
        
        void FReplicationWriter::HandleDeliveredRecord(
            const FReplicationRecord::FRecordInfo& RecordInfo,
            FReplicationInfo& Info,
            uint32 InternalIndex){
            // 1. 确认新 baseline
            if (RecordInfo.NewBaselineIndex != FDeltaCompressionBaselineManager::InvalidBaselineIndex)
            {
                check(RecordInfo.NewBaselineIndex == Info.PendingBaselineIndex);
                
                // 销毁旧的已确认 baseline
                if (Info.LastAckedBaselineIndex != InvalidBaselineIndex)
                {
                    BaselineManager->DestroyBaseline(
                        Parameters.ConnectionId, 
                        InternalIndex, 
                        Info.LastAckedBaselineIndex);
                }
                
                // 更新状态:PendingBaseline → LastAckedBaseline
                Info.LastAckedBaselineIndex = RecordInfo.NewBaselineIndex;
                Info.PendingBaselineIndex = InvalidBaselineIndex;
            }
        }

        10.7.3 ❌ 丢包处理:HandleDroppedRecord

        CPP
        // ReplicationWriter.cpp
        
        void FReplicationWriter::HandleDroppedRecord(
            const FReplicationRecord::FRecordInfo& RecordInfo,
            FReplicationInfo& Info,
            uint32 InternalIndex){
            // 1. 处理丢失的新 baseline
            if (RecordInfo.NewBaselineIndex != FDeltaCompressionBaselineManager::InvalidBaselineIndex)
            {
                // 关键:使用 LostBaseline 而不是 DestroyBaseline
                // LostBaseline 会将 baseline 的 ChangeMask 合并回连接 ChangeMask
                // 确保丢失的数据在下次发送时重新发送
                BaselineManager->LostBaseline(
                    Parameters.ConnectionId,
                    InternalIndex,
                    RecordInfo.NewBaselineIndex);
                
                Info.PendingBaselineIndex = InvalidBaselineIndex;
            }
        }

        10.7.4 ⚖️ Lost vs Destroy:ChangeMask 处理差异

        CPP
        // DeltaCompressionBaselineManager.cpp
        
        void FDeltaCompressionBaselineManager::LostBaseline(uint32 ConnId, uint32 ObjectIndex, uint32 BaselineIndex){
            // 丢包:合并 ChangeMask
            DestroyBaseline(ConnId, ObjectIndex, BaselineIndex, EChangeMaskBehavior::Merge);
        }
        
        void FDeltaCompressionBaselineManager::DestroyBaseline(uint32 ConnId, uint32 ObjectIndex, uint32 BaselineIndex){
            // 正常销毁:丢弃 ChangeMask
            DestroyBaseline(ConnId, ObjectIndex, BaselineIndex, EChangeMaskBehavior::Discard);
        }
        
        void FDeltaCompressionBaselineManager::DestroyBaseline(
            uint32 ConnId, uint32 ObjectIndex, uint32 BaselineIndex,
            EChangeMaskBehavior ChangeMaskBehavior){
            FObjectInfo* ObjectInfo = GetObjectInfo(ObjectIndex);
            FInternalBaseline& InternalBaseline = ObjectInfo->BaselinesForConnections[ConnId].Baselines[BaselineIndex];
            
            if (ChangeMaskBehavior == EChangeMaskBehavior::Merge)
            {
                // 将 baseline 的 ChangeMask 合并回连接 ChangeMask
                const uint32 ChangeMaskStride = ObjectInfo->ChangeMaskStride;
                ChangeMaskStorageType* ConnectionChangeMask = 
                    ObjectInfo->ChangeMasksForConnections + ChangeMaskStride * ConnId;
                
                for (uint32 WordIt = 0; WordIt < ChangeMaskStride; ++WordIt)
                {
                    ConnectionChangeMask[WordIt] |= InternalBaseline.ChangeMask[WordIt];
                }
            }
            
            ReleaseInternalBaseline(InternalBaseline);
        }

        为什么丢包要合并 ChangeMask? 🤔

        PLAINTEXT
        时刻 T1:发送 baseline[0],包含字段 {A, B, C} 📦
        时刻 T2:字段 D 变化,记录到连接 ChangeMask ✏️
        时刻 T3:baseline[0] 的包丢失 ❌
        
        如果不合并:下次发送只会发 {D},客户端缺少 {A, B, C} 💔
        如果合并:下次发送会发 {A, B, C, D},客户端状态完整 ✅

        10.8 🆕 初始状态 Delta 压缩

        10.8.1 🔄 与 Default State 做 Delta

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationOperations.cpp
        
        static bool bDeltaCompressInitialState = true;
        static FAutoConsoleVariableRef CVarbDeltaCompressInitialState(
            TEXT("net.Iris.DeltaCompressInitialState"),
            bDeltaCompressInitialState,
            TEXT("if true we compare with default state when serializing initial state.")
        );
        
        void FReplicationProtocolOperations::SerializeInitialStateWithMask(
            FNetSerializationContext& Context,
            const uint32* ChangeMaskData,
            const uint8* SrcObjectStateBuffer,
            const FReplicationProtocol* Protocol){
            if (!bDeltaCompressInitialState)
            {
                // 禁用时使用普通序列化
                SerializeWithMask(Context, ChangeMaskData, SrcObjectStateBuffer, Protocol);
                return;
            }
            
            FNetBitStreamWriter& Writer = *Context.GetBitStreamWriter();
            
            // 写 ChangeMask(初始状态通常大部分字段都是脏的,使用 ContainsMostlyOnes 提示)
            WriteSparseBitArray(&Writer, ChangeMaskData, Protocol->ChangeMaskBitCount,
                ESparseBitArraySerializationHint::ContainsMostlyOnes);
            
            // 遍历 ReplicationState
            for (uint32 StateIt = 0; StateIt < Protocol->ReplicationStateCount; ++StateIt)
            {
                const FReplicationStateDescriptor* CurrentDescriptor = ReplicationStateDescriptors[StateIt];
                const uint8* CurrentInternalStateBuffer = SrcObjectStateBuffer + CurrentDescriptor->ExternalOffset;
                
                // 关键:与 DefaultStateBuffer 做 delta
                const uint8* DefaultStateBuffer = CurrentDescriptor->DefaultStateBuffer;
                
                if (CurrentDescriptor->IsInitState())
                {
                    FReplicationStateOperations::SerializeDelta(
                        Context, CurrentInternalStateBuffer, DefaultStateBuffer, CurrentDescriptor);
                }
                else
                {
                    FReplicationStateOperations::SerializeDeltaWithMask(
                        Context, ChangeMask, CurrentChangeMaskBitOffset,
                        CurrentInternalStateBuffer, DefaultStateBuffer, CurrentDescriptor);
                }
            }
        }

        10.8.2 📦 Default State 的来源

        CPP
        // FReplicationStateDescriptor 在构建时从 CDO 量化得到 DefaultStateBuffer
        
        // ReplicationStateDescriptorBuilder.cpp(简化)void BuildDescriptor(UClass* Class, FReplicationStateDescriptor* Descriptor){
            // 获取 CDO
            UObject* CDO = Class->GetDefaultObject();
            
            // 分配 DefaultStateBuffer
            Descriptor->DefaultStateBuffer = FMemory::Malloc(Descriptor->InternalSize, Descriptor->InternalAlignment);
            
            // 将 CDO 的属性量化到 DefaultStateBuffer
            QuantizeObjectState(CDO, Descriptor->DefaultStateBuffer, Descriptor);
        }

        10.9 📊 性能模型与优化建议

        10.9.1 💾 内存开销模型

        PLAINTEXT
        每个启用 Delta 的对象的内存开销:
        
        Server 侧:
          - FObjectInfo:固定开销 ~64 bytes
          - BaselinesForConnections:MaxConnectionCount × MaxBaselineCount × sizeof(FInternalBaseline)
                                   = 100 × 2 × 32 = 6.4 KB(假设 100 连接)
          - ChangeMasksForConnections:MaxConnectionCount × ChangeMaskStride × 4
                                     = 100 × 4 × 4 = 1.6 KB(假设 128 个可变字段)
          - Baseline StateBuffer:RefCount × Protocol->InternalTotalSize
                                = 1~2 × 512 = 0.5~1 KB(假设 512 bytes 状态)
          
          单对象总计:~8-10 KB
        
        Client 侧:
          - StoredBaselines:MaxBaselineCount × Protocol->InternalTotalSize
                           = 2 × 512 = 1 KB
          
          单对象总计:~1 KB
        

        10.9.2 ⚡ CPU 开销分析

        PLAINTEXT
        发送侧(每帧每对象):
          1. CreateBaseline:O(ChangeMaskStride) - ChangeMask 复制
          2. SerializeWithMaskDelta:O(MemberCount) - 遍历成员
          3. 每个脏成员:SerializeDelta 开销取决于 serializer 实现
        
        接收侧(每帧每对象):
          1. DeserializeWithMaskDelta:O(MemberCount)
          2. 每个脏成员:DeserializeDelta
          3. StoreBaseline(如果有新 baseline):O(StateSize) memcpy

        10.9.3 📉 压缩率影响因素

        因素

        影响

        建议

        🎯 脏字段比例

        脏字段越少,压缩率越高

        减少不必要的 SetDirty

        📏 字段值变化幅度

        小幅变化可用更少 bits

        使用自定义 delta serializer

        ⏰ Baseline 新鲜度

        越新鲜,delta 越小

        适当降低 MinimumNumberOfFramesBetweenBaselines

        🔢 ChangeMask 稀疏度

        越稀疏,ChangeMask 压缩越好

        将相关字段放在一起

        10.9.4 💡 优化建议

        1. 🎯 选择合适的对象启用 Delta

        INI
        ; ✅ 推荐:字段多但变化少的对象
        +DeltaCompressionConfigs=(ClassName=/Script/Engine.Pawn)
        +DeltaCompressionConfigs=(ClassName=/Script/Engine.PlayerState)
        
        ; ❌ 不推荐:字段少或几乎每帧全变的对象; ❌ 不推荐:生命周期极短的对象

        2. ⏱️ 调整 Baseline 创建频率

        CPP
        // 🎮 变化平滑的游戏:增大间隔
        net.Iris.MinimumNumberOfFramesBetweenBaselines 120
        
        // 🚀 频繁瞬移/传送的游戏:减小间隔
        net.Iris.MinimumNumberOfFramesBetweenBaselines 30

        3. ✨ 自定义高效 Delta Serializer

        CPP
        // 对于位置等连续变化的数据,使用差值编码static void SerializeDelta(FNetSerializationContext& Context, const FNetSerializeDeltaArgs& Args){
            const FVector& Value = *reinterpret_cast<const FVector*>(Args.Source);
            const FVector& Prev = *reinterpret_cast<const FVector*>(Args.Prev);
            
            // 计算差值,使用更少的位数
            FVector Delta = Value - Prev;
            
            // 如果差值在小范围内,用更少的位数
            if (FMath::Abs(Delta.X) < 256 && FMath::Abs(Delta.Y) < 256 && FMath::Abs(Delta.Z) < 256)
            {
                Writer->WriteBool(true);  // 小范围标志
                Writer->WriteBits(Delta.X + 128, 8);
                Writer->WriteBits(Delta.Y + 128, 8);
                Writer->WriteBits(Delta.Z + 128, 8);
            }
            else
            {
                Writer->WriteBool(false);
                // 完整写入
                Serialize(Context, Args);
            }
        }

        10.10 🔧 调试与排查

        10.10.1 🎛️ 关键 CVars

        CVar

        默认值

        说明

        net.Iris.EnableDeltaCompression

        true

        总开关

        net.Iris.MinimumNumberOfFramesBetweenBaselines

        60

        Baseline 创建最小帧间隔

        net.Iris.DeltaCompressInitialState

        true

        初始状态是否与 default 做 delta

        10.10.2 🔍 调试技巧

        1. ✅ 确认 Delta 路径是否生效

        CPP
        // ReplicationReader.cpp 中有 trace scopeUE_NET_TRACE_SCOPE(DeltaCompressed, *Context.GetBitStreamReader(), Context.GetTraceCollector(), ENetTraceVerbosity::Trace);
        
        // 使用 Network Insights 或 Unreal Insights 查看

        2. 📋 检查 Baseline 状态

        CPP
        // 在 BaselineManager 中添加日志UE_LOG(LogIris, Log, TEXT("CreateBaseline: Object=%d, Conn=%d, Index=%d"), 
            ObjectIndex, ConnId, BaselineIndex);

        3. 💾 监控内存使用

        CPP
        // LLM 标签LLM_SCOPE_BYTAG(IrisState);
        
        // 使用 Unreal Insights 的 LLM 视图查看 IrisState 内存

        10.10.3 🚨 常见问题排查

        问题

        可能原因

        排查方法

        ❌ 不走 delta

        总开关关闭

        检查 net.Iris.EnableDeltaCompression

        ❌ 不走 delta

        协议不支持

        检查 ProtocolTraits 是否包含 DC 支持

        ❌ 不走 delta

        类未配置

        检查 DeltaCompressionConfigs

        📉 压缩率低

        脏字段太多

        减少不必要的属性变化

        💥 状态错乱

        Baseline 失效未处理

        检查 Conditionals 变化是否触发 invalidation

        💾 内存高

        对象太多

        降低 MaxDeltaCompressedObjectCount


        10.11 📚 关键源文件索引

        功能模块

        文件路径

        🗄️ Baseline 存储

        Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineStorage.h/cpp

        🎛️ Baseline 管理

        Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineManager.h/cpp

        ⚠️ 失效追踪

        Private/Iris/ReplicationSystem/DeltaCompression/DeltaCompressionBaselineInvalidationTracker.h/cpp

        💾 状态存储

        Private/Iris/ReplicationState/ReplicationStateStorage.h/cpp

        📤 发送侧序列化

        Private/Iris/ReplicationSystem/ReplicationWriter.cpp (SerializeObjectStateDelta)

        📥 接收侧反序列化

        Private/Iris/ReplicationSystem/ReplicationReader.cpp (DeserializeObjectStateDelta)

        📜 协议级操作

        Private/Iris/ReplicationSystem/ReplicationOperations.cpp (SerializeWithMaskDelta)

        📝 发送记录

        Private/Iris/ReplicationSystem/ReplicationRecord.h

        🎯 NetSerializer 接口

        Public/Iris/Serialization/NetSerializer.h (SerializeDelta/DeserializeDelta)

        🔄 默认 Delta 实现

        Public/Iris/Serialization/NetSerializerBuilder.inl


        本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)

        最后更新于 April 13, 2026
        On this page
        暂无目录