页面加载中
博客快捷键
按住 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 18, 202589 分钟 阅读216 次阅读

        🔍 Iris 网络复制系统技术分析 - 第五部分:过滤系统 (Filtering)

        想象一下,你是一个超级忙碌的快递员,要给一个大型小区的1000户居民送快递。如果你每次都把所有快递都带上,挨家挨户问"这个是你的吗?"——那你一天也送不完几单!聪明的做法是:先筛选出每个区域需要的快递,只带相关的包裹去对应的楼栋。

        Iris 的过滤系统就是这样一个"智能快递分拣员"🚀,它帮助服务器决定:哪些游戏对象需要同步给哪些玩家。


        📚 5.1 过滤系统概述

        🎯 过滤的目的与意义

        在多人游戏中,服务器上可能存在成千上万个需要同步的对象(玩家、NPC、道具、子弹等)。如果把所有对象的数据都发送给每个玩家,会导致:

        问题

        后果

        🔥 带宽爆炸

        网络拥堵,玩家卡顿

        💻 客户端过载

        CPU/内存吃不消

        🔓 安全隐患

        玩家可能通过作弊看到不该看到的信息

        ⚡ 延迟增加

        重要数据被不重要的数据挤占

        过滤系统的核心任务:为每个连接(玩家)筛选出"相关"的对象,只同步真正需要的数据。

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │                    🌍 服务器上的所有对象                          │
        │  [玩家A] [玩家B] [玩家C] [NPC1] [NPC2] [道具1] [道具2] [子弹]...   │
        └─────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
                            ┌─────────────────┐
                            │   🔍 过滤系统    │
                            │  "谁需要什么?"  │
                            └─────────────────┘
                                      │
                   ┌──────────────────┼──────────────────┐
                   ▼                  ▼                  ▼
            ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
            │  玩家A连接   │    │  玩家B连接   │    │  玩家C连接   │
            │ [A][NPC1]   │    │ [B][NPC2]   │    │ [C][道具1]  │
            │ [道具2]     │    │ [子弹]      │    │ [NPC1]     │
            └─────────────┘    └─────────────┘    └─────────────┘

        🏷️ 过滤器类型分类

        Iris 提供了多种过滤器,就像快递分拣有不同的规则一样:

        过滤器类型

        比喻

        用途

        空间过滤器 (GridFilter)

        📍 "只送本小区的快递"

        基于距离/位置过滤

        所有者过滤 (Owner)

        🔐 "只有收件人能收"

        只同步给拥有者

        连接过滤 (Connection)

        📋 "VIP专属配送"

        针对特定连接的过滤

        组过滤 (Group)

        🏢 "按楼栋分批送"

        基于分组的批量过滤

        空操作过滤 (Nop)

        ✅ "全部放行"

        不做任何过滤

        全部过滤 (FilterOut)

        ❌ "全部拦截"

        过滤掉所有对象

        ⏱️ 过滤流程时序

        每一帧,过滤系统都会按照以下顺序执行:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────┐
        │                     📊 每帧过滤流程                                 │
        ├────────────────────────────────────────────────────────────────────┤
        │                                                                    │
        │  1️⃣ ResetRemovedConnections()     ← 清理已断开的连接               │
        │                    ↓                                               │
        │  2️⃣ InitNewConnections()          ← 初始化新连接                   │
        │                    ↓                                               │
        │  3️⃣ UpdateObjectsInScope()        ← 更新范围内的对象               │
        │                    ↓                                               │
        │  4️⃣ UpdateGroupExclusionFiltering() ← 组排除过滤(优先级最高)     │
        │                    ↓                                               │
        │  5️⃣ UpdateGroupInclusionFiltering() ← 组包含过滤                   │
        │                    ↓                                               │
        │  6️⃣ UpdateOwnerAndConnectionFiltering() ← 所有者和连接过滤         │
        │                    ↓                                               │
        │  7️⃣ UpdateSubObjectFilters()      ← 子对象过滤                     │
        │                    ↓                                               │
        │  8️⃣ PreUpdateObjectScopeHysteresis() ← 滞后预处理                  │
        │                    ↓                                               │
        │  9️⃣ UpdateDynamicFilters()        ← 动态过滤器(如空间过滤)       │
        │                    ↓                                               │
        │  🔟 FilterNonRelevantObjects()    ← 最终过滤非相关对象              │
        │                                                                    │
        └────────────────────────────────────────────────────────────────────┘

        💡 小贴士:过滤是有优先级的!组排除过滤优先级最高——如果一个对象被组排除了,后面的过滤器都不会再考虑它。


        🧱 5.2 UNetObjectFilter 基类

        UNetObjectFilter 是所有过滤器的"老祖宗",定义了过滤器必须遵守的"家规"。理解这个基类是深入掌握整个过滤系统的关键。

        📋 Filter 接口定义

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        UCLASS(Abstract, MinimalAPI)
        class UNetObjectFilter : public UObject
        {
            GENERATED_BODY()
        
        public:
            // 🎬 生命周期
            void Init(const FNetObjectFilterInitParams& Params);
            void Deinit();
            
            // 🔌 连接管理
            virtual void AddConnection(uint32 ConnectionId);
            virtual void RemoveConnection(uint32 ConnectionId);
            
            // 📦 对象管理(子类必须实现)
            virtual bool AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams&) PURE_VIRTUAL;
            virtual void RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo&) PURE_VIRTUAL;
            virtual void UpdateObjects(FNetObjectFilterUpdateParams&);
            
            // 🔍 过滤流程
            virtual void PreFilter(FNetObjectPreFilteringParams&);
            virtual void Filter(FNetObjectFilteringParams&);  // ⭐ 核心方法
            virtual void PostFilter(FNetObjectPostFilteringParams&);
            
            // 🏷️ 特性查询
            ENetFilterTraits GetFilterTraits() const;
            bool HasFilterTrait(ENetFilterTraits FilterTrait) const;
            
        protected:
            // 子类可访问的成员
            const UE::Net::Private::FNetRefHandleManager* NetRefHandleManager = nullptr;
        };

        🔧 初始化参数详解

        过滤器初始化时会收到一个 FNetObjectFilterInitParams 结构,包含了过滤器运行所需的所有上下文信息:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        struct FNetObjectFilterInitParams
        {
            // 所属的复制系统实例
            TObjectPtr<UReplicationSystem> ReplicationSystem = nullptr;
            
            // 可选的配置对象(如 UNetObjectGridFilterConfig)
            UNetObjectFilterConfig* Config = nullptr;
            
            // 系统支持的最大复制对象数量(绝对上限)
            uint32 AbsoluteMaxNetObjectCount = 0;
            
            // 当前最大内部索引(可能在运行时增长)
            uint32 CurrentMaxInternalIndex = 0;
            
            // 系统支持的最大连接数
            uint32 MaxConnectionCount = 0;
        };

        为什么需要这些参数?

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              🔧 初始化参数的作用                                │├────────────────────────────────────────────────────────────────┤│                                                                ││  AbsoluteMaxNetObjectCount = 65536                            ││  └─→ 过滤器预分配位数组大小,避免运行时频繁扩容                ││                                                                ││  CurrentMaxInternalIndex = 1000                               ││  └─→ 当前实际使用的索引范围,优化遍历                          ││                                                                ││  MaxConnectionCount = 100                                     ││  └─→ 为每个连接预分配过滤状态数组                              ││                                                                ││  Config = UNetObjectGridFilterConfig*                         ││  └─→ 读取配置参数(网格大小、剔除距离等)                      ││                                                                │└────────────────────────────────────────────────────────────────┘

        ➕ AddObject / ➖ RemoveObject

        当一个游戏对象需要被某个过滤器管理时,系统会调用 AddObject;当对象销毁或不再需要过滤时,调用 RemoveObject。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        // 添加对象到过滤器// 返回值:true = 成功添加,false = 添加失败(对象不适合此过滤器)virtual bool AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams& Params) PURE_VIRTUAL;
        
        // 从过滤器移除对象virtual void RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo& Info) PURE_VIRTUAL;

        AddObject 参数详解:

        CPP
        struct FNetObjectFilterAddObjectParams
        {
            // 输出参数:过滤器特定的信息,存储在对象上
            // 初始值为零,由过滤器填充(如位置偏移、索引等)
            FNetObjectFilteringInfo& OutInfo;
            
            // 配置预设名称(如 "LongRange", "ShortRange")
            FName ProfileName;
            
            // 实例协议:描述对象的源状态数据布局
            const UE::Net::FReplicationInstanceProtocol* InstanceProtocol;
            
            // 复制协议:描述对象的内部状态数据布局
            const UE::Net::FReplicationProtocol* Protocol;
        };

        FNetObjectFilteringInfo 的内部结构:

        CPP
        // 每个对象存储 8 字节的过滤器专用数据struct alignas(8) FNetObjectFilteringInfo
        {
            uint16 Data[4];  // 4 个 16 位字段,共 64 位
        };
        
        // GridFilter 的使用方式(FObjectLocationInfo):// Data[0] = 位置状态偏移量// Data[1] = 位置状态索引// Data[2] = FPerObjectInfo 索引(低 16 位)// Data[3] = FPerObjectInfo 索引(高 16 位)

        生活类比 🏪:

        • AddObject = 新商品上架,需要登记到库存系统,记录货架位置

        • RemoveObject = 商品下架,从库存系统删除,清理货架位置

        AddObject 的典型实现流程:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              ➕ AddObject 实现流程                              │├────────────────────────────────────────────────────────────────┤│                                                                ││  1. 检查对象是否适合此过滤器                                    ││     │                                                          ││     ├─→ GridFilter: 对象必须有位置信息                         ││     ├─→ ConnectionFilter: 任何对象都可以                       ││     └─→ 如果不适合,返回 false                                 ││                                                                ││  2. 从协议中提取需要的信息                                      ││     │                                                          ││     ├─→ 查找 RepTag_WorldLocation 获取位置偏移                 ││     ├─→ 查找 RepTag_CullDistanceSqr 获取剔除距离               ││     └─→ 存储到 OutInfo 中                                      ││                                                                ││  3. 分配内部资源                                                ││     │                                                          ││     ├─→ GridFilter: 分配 FPerObjectInfo 槽位                   ││     ├─→ 将对象添加到相关网格单元                               ││     └─→ 初始化帧计数器                                         ││                                                                ││  4. 返回 true 表示成功                                         ││                                                                │└────────────────────────────────────────────────────────────────┘

        🔄 PreFilter / Filter / PostFilter

        过滤流程分三个阶段,就像做菜有"备菜→烹饪→装盘"三步:

        阶段

        方法

        作用

        🥬 准备阶段

        PreFilter()

        准备过滤所需的数据,每帧调用一次

        🍳 执行阶段

        Filter()

        核心! 为每个连接执行过滤逻辑

        🍽️ 收尾阶段

        PostFilter()

        清理临时数据,统计信息,每帧调用一次

        Filter 参数详解:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        struct FNetObjectFilteringParams
        {
            // ⭐ 核心输出:位数组,设置哪些对象允许复制
            // 初始值未定义!过滤器必须显式设置每个相关对象的位
            UE::Net::FNetBitArrayView OutAllowedObjects;
            
            // 所有对象的过滤信息(由 AddObject 时填充)
            TArrayView<const FNetObjectFilteringInfo> FilteringInfos;
            
            // 所有对象的状态缓冲区(可用于读取位置等数据)
            const UE::Net::TNetChunkedArray<uint8*>* StateBuffers = nullptr;
            
            // 当前正在过滤的连接 ID
            uint32 ConnectionId = 0;
            
            // 该连接的视图信息(位置、方向、FOV)
            UE::Net::FReplicationView View;
        };

        FReplicationView 结构:

        CPP
        struct FReplicationView
        {
            // 支持多视图(如分屏游戏)
            struct FView
            {
                FVector Pos;           // 视图位置
                FVector Dir;           // 视图方向
                float FoVRadians;      // 视野角度(弧度)
            };
            
            TArray<FView, TInlineAllocator<2>> Views;  // 通常 1-2 个视图
        };

        Filter 方法的调用时序:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔄 每帧过滤调用时序                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  帧开始                                                        │
        │     │                                                          │
        │     ▼                                                          │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  PreFilter() - 所有过滤器各调用一次                      │  │
        │  │  ├─→ GridFilter: 增加帧计数器,重置统计                  │  │
        │  │  ├─→ ConnectionFilter: 准备连接状态                      │  │
        │  │  └─→ 其他过滤器: 各自的准备工作                          │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │     │                                                          │
        │     ▼                                                          │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  for each Connection:                                    │  │
        │  │      for each Filter:                                    │  │
        │  │          Filter(ConnectionId, View) ← 核心过滤逻辑       │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │     │                                                          │
        │     ▼                                                          │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  PostFilter() - 所有过滤器各调用一次                     │  │
        │  │  ├─→ GridFilter: 输出 CSV 统计                           │  │
        │  │  ├─→ 清理临时数据                                        │  │
        │  │  └─→ 性能指标收集                                        │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │     │                                                          │
        │     ▼                                                          │
        │  帧结束                                                        │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        Filter 实现的关键点(基于真实源码):

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.cpp:134-258
        
        void UNetObjectGridFilter::Filter(FNetObjectFilteringParams& Params){
            IRIS_PROFILER_SCOPE(UNetObjectGridFilter_Filter);
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段1:更新连接的活跃单元格列表
            // ═══════════════════════════════════════════════════════════════
            FPerConnectionInfo& ConnectionInfo = PerConnectionInfos[Params.ConnectionId];
            TArray<FCellAndTimestamp, TInlineAllocator<32>> PrevCells = ConnectionInfo.RecentCells;
            ConnectionInfo.RecentCells.Reset();
        
            // 🎯 为每个视图计算当前所在的单元格
            TArray<FCellAndTimestamp, TInlineAllocator<32>> NewCells;
            for (FReplicationView::FView& View : Params.View.Views)
            {
                FCellAndTimestamp CellAndTimestamp;
                CellAndTimestamp.Timestamp = FrameIndex;  // 记录帧时间戳
                CalculateCellCoord(CellAndTimestamp.Cell, View.Pos);  // 世界坐标转单元格坐标
                NewCells.AddUnique(CellAndTimestamp);
        
                // 🔄 从 PrevCells 中移除仍然有效的单元格(避免重复处理)
                for (const FCellAndTimestamp& PrevCell : PrevCells)
                {
                    if ((PrevCell.Cell.X == CellAndTimestamp.Cell.X) & 
                        (PrevCell.Cell.Y == CellAndTimestamp.Cell.Y))
                    {
                        PrevCells.RemoveAtSwap(static_cast<int32>(&PrevCell - PrevCells.GetData()));
                        break;
                    }
                }
            }
        
            // ⏰ 非精确模式下:保留最近几帧访问过的单元格(ViewPosRelevancyFrameCount)
            if (!Config->bUseExactCullDistance)
            {
                const uint32 MaxFrameCount = Config->ViewPosRelevancyFrameCount;
                for (const FCellAndTimestamp& PrevCell : PrevCells)
                {
                    if ((FrameIndex - PrevCell.Timestamp) > MaxFrameCount)
                    {
                        continue;  // 太旧了,丢弃
                    }
                    NewCells.Add(PrevCell);  // 仍然相关,保留
                }
            }
        
            // 💾 保存新的单元格列表
            ConnectionInfo.RecentCells = NewCells;
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段2:根据单元格设置允许的对象
            // ═══════════════════════════════════════════════════════════════
            FNetBitArrayView AllowedObjects = Params.OutAllowedObjects;
            AllowedObjects.ClearAllBits();  // 🧹 先清空所有位
        
            if (Config->bUseExactCullDistance)
            {
                // ═══════════════════════════════════════════════════════════
                // 精确距离模式:逐对象检查距离
                // ═══════════════════════════════════════════════════════════
                for (const FCellAndTimestamp& CellAndTimestamp : NewCells)
                {
                    if (FCellObjects* Objects = Cells.Find(CellAndTimestamp.Cell))
                    {
                        for (const uint32 ObjectIndex : Objects->ObjectIndices)
                        {
                            // 📍 获取对象信息
                            const FObjectLocationInfo& ObjectLocationInfo = 
                                static_cast<const FObjectLocationInfo&>(Params.FilteringInfos[ObjectIndex]);
                            const FPerObjectInfo& PerObjectInfo = ObjectInfos[ObjectLocationInfo.GetInfoIndex()];
        
                            // 🎯 检查是否有任何视图在剔除距离内
                            for (const FReplicationView::FView& View : Params.View.Views)
                            {
                                const double DistSq = PerObjectInfo.GetCullDistanceSq();
                                const double ObjectToViewDistSq = FVector::DistSquared(
                                    PerObjectInfo.Position, View.Pos);
        
                                if (ObjectToViewDistSq <= DistSq)
                                {
                                    // ✅ 在范围内:重置帧计数器(滞后机制)
                                    ConnectionInfo.RecentObjectFrameCount.Add(
                                        ObjectIndex, 
                                        PerObjectInfo.FrameCountBeforeCulling);
                                    break;  // 一个视图满足即可,无需检查其他
                                }
                            }
                        }
                    }
                }
        
                // 🔢 处理帧计数器并设置 AllowedObjects
                for (TMap<uint32, uint16>::TIterator It = ConnectionInfo.RecentObjectFrameCount.CreateIterator(); 
                     It; ++It)
                {
                    if (It->Value > 0)
                    {
                        It->Value--;  // 递减计数器
                        AllowedObjects.SetBit(It->Key);  // ✅ 仍然允许复制
                    }
                    else
                    {
                        It.RemoveCurrent();  // 🗑️ 计数器归零,移除追踪
                    }
                }
            }
            else
            {
                // ═══════════════════════════════════════════════════════════
                // 网格模式:单元格内所有对象直接允许(更快)
                // ═══════════════════════════════════════════════════════════
                for (const FCellAndTimestamp& CellAndTimestamp : NewCells)
                {
                    if (FCellObjects* Objects = Cells.Find(CellAndTimestamp.Cell))
                    {
                        for (const uint32 ObjectIndex : Objects->ObjectIndices)
                        {
                            AllowedObjects.SetBit(ObjectIndex);  // ✅ 直接允许
                        }
                    }
                }
            }
        }

        关键数据结构:

        CPP
        // 单元格坐标(2D网格)struct FCellCoord
        {
            int32 X;
            int32 Y;
        };
        
        // 带时间戳的单元格(用于视图位置相关性追踪)struct FCellAndTimestamp
        {
            FCellCoord Cell;
            uint32 Timestamp;  // 帧索引
        };
        
        // 每连接信息struct FPerConnectionInfo
        {
            TArray<FCellAndTimestamp> RecentCells;       // 最近访问的单元格
            TMap<uint32, uint16> RecentObjectFrameCount; // 对象索引 → 剩余帧计数
        };

        两种过滤模式对比:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │                    🎯 GridFilter 两种模式                       │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  📦 网格模式 (bUseExactCullDistance = false)                   │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  • 更快:只检查单元格归属                                │  │
        │  │  • 粒度粗:单元格内所有对象都允许                        │  │
        │  │  • 支持 ViewPosRelevancyFrameCount 滞后                  │  │
        │  │  • 适合:大量对象、对精度要求不高的场景                  │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  🎯 精确距离模式 (bUseExactCullDistance = true)                │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  • 更精确:逐对象计算距离                                │  │
        │  │  • 支持 FrameCountBeforeCulling 滞后                     │  │
        │  │  • 开销更大:需要计算 DistSquared                        │  │
        │  │  • 适合:对象数量适中、需要精确剔除的场景                │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🏷️ FilterTraits 特性

        过滤器可以声明自己的"特性",让系统知道它的能力和需求:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        enum class ENetFilterTraits : uint8
        {
            None = 0x00,
            
            // 🗺️ 空间过滤器标记
            // 设置此标记表示过滤器基于 WorldLocation 进行过滤
            // 系统会为此类过滤器提供位置相关的优化
            Spatial = 0x01,
            
            // 🔄 需要更新标记
            // 设置此标记后,系统会每帧调用 UpdateObjects()
            // 用于需要追踪对象状态变化的过滤器
            NeedsUpdate = 0x02,
        };
        ENUM_CLASS_FLAGS(ENetFilterTraits);

        特性的设置与查询:

        CPP
        class UNetObjectFilter
        {
        protected:
            // 子类在 OnInit 中调用此方法添加特性
            void AddFilterTraits(ENetFilterTraits Traits)
            {
                FilterTraits |= Traits;
            }
            
        public:
            // 查询过滤器是否具有某特性
            bool HasFilterTrait(ENetFilterTraits Trait) const
            {
                return EnumHasAnyFlags(FilterTraits, Trait);
            }
            
        private:
            ENetFilterTraits FilterTraits = ENetFilterTraits::None;
        };
        
        // GridFilter 的初始化示例void UNetObjectGridFilter::OnInit(const FNetObjectFilterInitParams& Params){
            // 声明自己是空间过滤器
            AddFilterTraits(ENetFilterTraits::Spatial);
            // ...
        }

        为什么需要特性标记?

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🏷️ FilterTraits 的作用                            │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  Spatial 特性的影响:                                          │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  • 系统知道此过滤器需要位置信息                          │  │
        │  │  • 对象添加时会查找 RepTag_WorldLocation                 │  │
        │  │  • 可以与 WorldLocations 系统集成                        │  │
        │  │  • 优先级系统可以复用位置数据                            │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  NeedsUpdate 特性的影响:                                      │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  • 系统每帧调用 UpdateObjects() 方法                     │  │
        │  │  • 用于追踪对象位置变化、状态变化                        │  │
        │  │  • 不设置此标记可以节省不必要的调用开销                  │  │
        │  │  • GridFilter 通过轮询机制更新,不需要此标记             │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  想象你是快递站调度员:                                        │
        │  • Spatial 标记 = "这个快递员只负责固定区域"                  │
        │  • NeedsUpdate 标记 = "这个快递员需要每天更新路线"            │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🔌 静态过滤器句柄

        除了动态过滤器(如 GridFilter),Iris 还定义了几个静态过滤器句柄用于特殊用途:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectFilter.h
        
        // 无效句柄 - 表示对象没有分配过滤器constexpr FNetObjectFilterHandle InvalidNetObjectFilterHandle = 0;
        
        // 所有者过滤 - 只同步给拥有该对象的连接constexpr FNetObjectFilterHandle ToOwnerFilterHandle = 1;
        
        // 连接过滤 - 内部使用,用于每连接的自定义过滤constexpr FNetObjectFilterHandle ConnectionFilterHandle = 2;

        句柄类型判断:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.cpp
        
        class FNetObjectFilterHandleUtil
        {
        public:
            // 检查是否为无效句柄
            static bool IsInvalidHandle(FNetObjectFilterHandle Handle);
            
            // 检查是否为动态过滤器(如 GridFilter)
            static bool IsDynamicFilter(FNetObjectFilterHandle Handle);
            
            // 检查是否为静态过滤器(ToOwner, Connection)
            static bool IsStaticFilter(FNetObjectFilterHandle Handle);
            
        private:
            // 最高位用于区分动态/静态过滤器
            static constexpr FNetObjectFilterHandle DynamicNetObjectFilterHandleFlag = 
                1U << (sizeof(FNetObjectFilterHandle) * 8U - 1U);
        };

        🎛️ 5.3 内置过滤器

        Iris 提供了几个开箱即用的过滤器,覆盖最常见的场景。这些过滤器从最简单到最复杂,展示了过滤器接口的不同使用方式。

        ✅ NopNetObjectFilter(空操作过滤器)

        "来者不拒,全部放行!"

        这是最简单的过滤器——它什么都不过滤,所有对象都允许复制。它的实现是理解过滤器接口的最佳起点。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NopNetObjectFilter.h
        
        // 配置类(空实现,不需要任何配置)UCLASS(transient, MinimalAPI)
        class UNopNetObjectFilterConfig final : public UNetObjectFilterConfig
        {
            GENERATED_BODY()
        };
        
        // 过滤器类UCLASS()
        class UNopNetObjectFilter final : public UNetObjectFilter
        {
            GENERATED_BODY()
            
        protected:
            // 初始化 - 什么都不做
            virtual void OnInit(const FNetObjectFilterInitParams&) override;
            virtual void OnDeinit() override {}
            
            // 索引增长 - 不需要处理
            virtual void OnMaxInternalNetRefIndexIncreased(uint32 NewMaxInternalIndex) override {}
            
            // 对象管理 - 接受所有对象,不存储任何信息
            virtual bool AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams&) override;
            virtual void RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo&) override;
            
            // 核心过滤逻辑
            virtual void Filter(FNetObjectFilteringParams&) override;
        };

        实现源码:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NopNetObjectFilter.cpp
        
        void UNopNetObjectFilter::OnInit(const FNetObjectFilterInitParams& Params){
            // 空实现 - 不需要任何初始化
        }
        
        bool UNopNetObjectFilter::AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams& Params){
            // 接受所有对象,不需要存储任何信息
            return true;
        }
        
        void UNopNetObjectFilter::RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo& Info){
            // 空实现 - 没有需要清理的资源
        }
        
        void UNopNetObjectFilter::Filter(FNetObjectFilteringParams& Params){
            // 核心逻辑:设置所有位为1,表示全部允许
            // 就像没有应用任何动态过滤一样
            Params.OutAllowedObjects.SetAllBits();
        }

        使用场景 🎮:

        • 调试时临时禁用过滤

        • 小规模游戏,所有玩家都需要看到所有对象

        • 作为默认过滤器的占位符

        • 测试网络复制功能时排除过滤器的影响

        PLAINTEXT
        ┌─────────────────────────────────────┐
        │      NopFilter: "全部放行!"         │
        │                                     │
        │  输入: [A][B][C][D][E][F][G][H]     │
        │         ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓     │
        │  输出: [A][B][C][D][E][F][G][H]     │
        │         ✓  ✓  ✓  ✓  ✓  ✓  ✓  ✓     │
        │                                     │
        │  实现: SetAllBits() - O(N/64) 位操作│
        └─────────────────────────────────────┘

        ❌ FilterOutNetObjectFilter(始终过滤)

        "一个都别想过!"

        与 Nop 相反,这个过滤器拒绝所有对象。它的实现与 NopFilter 几乎相同,只是 Filter 方法的行为相反。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/FilterOutNetObjectFilter.h
        
        // 配置类(空实现)UCLASS(transient, MinimalAPI)
        class UFilterOutNetObjectFilterConfig final : public UNetObjectFilterConfig
        {
            GENERATED_BODY()
        };
        
        // 过滤器类UCLASS()
        class UFilterOutNetObjectFilter final : public UNetObjectFilter
        {
            GENERATED_BODY()
            
        protected:
            virtual void OnInit(const FNetObjectFilterInitParams&) override;
            virtual void OnDeinit() override {}
            virtual void OnMaxInternalNetRefIndexIncreased(uint32 NewMaxInternalIndex) override {}
            virtual bool AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams&) override;
            virtual void RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo&) override;
            virtual void Filter(FNetObjectFilteringParams&) override;
        };

        实现源码:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/FilterOutNetObjectFilter.cpp
        
        void UFilterOutNetObjectFilter::OnInit(const FNetObjectFilterInitParams& Params){
            // 空实现
        }
        
        bool UFilterOutNetObjectFilter::AddObject(uint32 ObjectIndex, FNetObjectFilterAddObjectParams& Params){
            return true;  // 接受对象,但在过滤时会全部拒绝
        }
        
        void UFilterOutNetObjectFilter::RemoveObject(uint32 ObjectIndex, const FNetObjectFilteringInfo& Info){
            // 空实现
        }
        
        void UFilterOutNetObjectFilter::Filter(FNetObjectFilteringParams& Params){
            // 核心逻辑:清除所有位,表示全部禁止
            Params.OutAllowedObjects.ClearAllBits();
        }

        使用场景 🎮:

        • 临时"冻结"某类对象的复制

        • 测试用途(验证对象确实被过滤)

        • 作为"黑名单"过滤器的基础

        • 在特定条件下完全禁用某类对象的同步

        PLAINTEXT
        ┌─────────────────────────────────────┐
        │     FilterOut: "全部拦截!"          │
        │                                     │
        │  输入: [A][B][C][D][E][F][G][H]     │
        │         ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓     │
        │  输出: [ ][ ][ ][ ][ ][ ][ ][ ]    │
        │         ✗  ✗  ✗  ✗  ✗  ✗  ✗  ✗     │
        │                                     │
        │  实现: ClearAllBits() - O(N/64)    │
        └─────────────────────────────────────┘

        NopFilter vs FilterOutFilter 对比:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              📊 简单过滤器对比                                  │├────────────────────────────────────────────────────────────────┤│                                                                ││  特性              NopFilter           FilterOutFilter         ││  ─────────────────────────────────────────────────────────────││  Filter 行为       SetAllBits()        ClearAllBits()          ││  默认状态          全部允许            全部禁止                 ││  内存占用          0                   0                       ││  CPU 开销          O(N/64)             O(N/64)                 ││  存储状态          无                  无                       ││  适用场景          调试/小规模游戏     测试/临时禁用            ││                                                                ││  💡 这两个过滤器展示了过滤器接口的最简实现                      ││     实际项目中很少直接使用,但对于理解接口很有帮助              ││                                                                │└────────────────────────────────────────────────────────────────┘

        🗺️ NetObjectGridFilter(空间网格过滤)

        "只同步你附近的东西!"

        这是最常用也最复杂的过滤器,基于空间位置进行过滤。想象把游戏世界划分成一个个网格,只同步玩家所在网格及附近网格中的对象。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        UCLASS(transient, config=Engine)
        class UNetObjectGridFilterConfig : public UNetObjectFilterConfig
        {
            GENERATED_BODY()
        
        public:
            // 🕐 视点位置相关性帧数
            UPROPERTY(Config)
            uint32 ViewPosRelevancyFrameCount = 2;
            
            // ⏱️ 剔除前等待帧数(防抖动)
            UPROPERTY(Config)
            uint16 DefaultFrameCountBeforeCulling = 4;
            
            // 📐 网格单元尺寸
            UPROPERTY(Config)
            float CellSizeX = 20000.0f;  // 200米
            
            UPROPERTY(Config)
            float CellSizeY = 20000.0f;  // 200米
            
            // 📏 剔除距离
            UPROPERTY(Config)
            float MaxCullDistance = 0.0f;
            
            UPROPERTY(Config)
            float DefaultCullDistance = 15000.0f;  // 150米
            
            // 🎯 是否使用精确距离计算
            UPROPERTY(Config)
            bool bUseExactCullDistance = true;
        };

        工作原理图解:

        PLAINTEXT
        ┌──────────────────────────────────────────────────────────────────┐
        │                    🗺️ 游戏世界网格划分                            │
        ├──────────────────────────────────────────────────────────────────┤
        │                                                                  │
        │    ┌─────┬─────┬─────┬─────┬─────┐                              │
        │    │     │     │ 🧟  │     │     │  ← 远处的僵尸,不同步         │
        │    ├─────┼─────┼─────┼─────┼─────┤                              │
        │    │     │ 🎁  │     │     │     │  ← 远处的宝箱,不同步         │
        │    ├─────┼─────┼─────┼─────┼─────┤                              │
        │    │     │     │ 🧟  │ 🎁  │     │  ← 这些在范围内,同步!       │
        │    ├─────┼─────┼─────┼─────┼─────┤                              │
        │    │     │ 🎁  │ 👤  │ 🧟  │     │  ← 玩家位置 + 附近对象        │
        │    ├─────┼─────┼─────┼─────┼─────┤     都需要同步               │
        │    │     │     │ 🧟  │     │     │                              │
        │    └─────┴─────┴─────┴─────┴─────┘                              │
        │                                                                  │
        │    👤 = 玩家位置    🧟 = 僵尸    🎁 = 宝箱                        │
        │    ━━ = 剔除距离范围                                             │
        │                                                                  │
        └──────────────────────────────────────────────────────────────────┘

        🔌 NetObjectConnectionFilter(连接过滤)

        "这个包裹只能送给张三!"

        允许为每个对象单独设置:它可以被哪些连接(玩家)看到。这是一个动态预轮询过滤器,支持每连接级别的过滤控制。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectConnectionFilter.h
        
        // 配置类 - 限制过滤器管理的最大对象数UCLASS(transient, MinimalAPI)
        class UNetObjectConnectionFilterConfig : public UNetObjectFilterConfig
        {
            GENERATED_BODY()
        public:
            // 最大对象数量(不适合处理大量对象,推荐使用静态连接过滤 API)
            UPROPERTY(Config)
            uint16 MaxObjectCount = 4096;
        };
        
        UCLASS(transient, MinimalAPI)
        class UNetObjectConnectionFilter : public UNetObjectFilter
        {
            GENERATED_BODY()
        public:
            // 设置对象对特定连接的复制状态
            IRISCORE_API void SetReplicateToConnection(
                FNetRefHandle RefHandle, 
                uint32 ConnectionId, 
                ENetFilterStatus FilterStatus
            );
            
        protected:
            // 过滤信息 - 存储本地对象索引
            struct FFilteringInfo : public FNetObjectFilteringInfo
            {
                void SetLocalObjectIndex(uint16 Index) { Data[0] = Index; }
                uint16 GetLocalObjectIndex() const { return Data[0]; }
            };
            
            // 每连接的过滤状态
            struct FPerConnectionInfo
            {
                FNetBitArray ReplicationEnabledObjects;  // 每连接启用的对象位图
            };
            
            TArray<uint32> LocalToNetRefIndex;           // 本地索引 → 网络对象索引
            TArray<FPerConnectionInfo> PerConnectionInfos;
            FNetBitArray UsedLocalInfoIndices;           // 已使用的本地索引
            bool bObjectRemoved = false;                 // 优化标志
        };

        实现细节分析:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Prioritization/NetObjectConnectionFilter.cpp
        
        // SetReplicateToConnection 的核心实现void UNetObjectConnectionFilter::SetReplicateToConnection(
            FNetRefHandle RefHandle, 
            uint32 ConnectionId, 
            ENetFilterStatus FilterStatus){
            // 1. 获取对象的内部索引
            const FInternalNetRefIndex ObjectIndex = GetObjectIndex(RefHandle);
            
            // 2. 获取过滤信息中存储的本地索引
            const FFilteringInfo* FilteringInfo = static_cast<const FFilteringInfo*>(
                GetFilteringInfo(ObjectIndex));
            const uint16 LocalIndex = FilteringInfo->GetLocalObjectIndex();
            
            // 3. 设置该连接对该对象的过滤状态
            FPerConnectionInfo& PerConnectionInfo = PerConnectionInfos[ConnectionId];
            PerConnectionInfo.ReplicationEnabledObjects.SetBitValue(
                LocalIndex, 
                FilterStatus == ENetFilterStatus::Allow
            );
        }
        
        // Filter 方法 - 核心过滤逻辑void UNetObjectConnectionFilter::Filter(FNetObjectFilteringParams& Params){
            FNetBitArrayView& AllowedObjects = Params.OutAllowedObjects;
            AllowedObjects.ClearAllBits();  // 默认全部禁止
            
            FPerConnectionInfo& ConnectionInfo = PerConnectionInfos[Params.ConnectionId];
            
            // 遍历该连接允许的对象,设置允许位
            ConnectionInfo.ReplicationEnabledObjects.ForAllSetBits(
                [&AllowedObjects, &ToNetRefIndex = LocalToNetRefIndex](uint32 LocalObjectIndex)
                {
                    const uint32 ObjectIndex = ToNetRefIndex[LocalObjectIndex];
                    AllowedObjects.SetBit(ObjectIndex);
                }
            );
        }

        本地索引映射机制:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              🔄 本地索引映射                                    │├────────────────────────────────────────────────────────────────┤│                                                                ││  为什么需要本地索引?                                           ││  ┌─────────────────────────────────────────────────────────┐  ││  │  全局对象索引可能很大(0~65535),但 ConnectionFilter    │  ││  │  只管理少量对象(最多 4096 个)。使用本地索引可以:       │  ││  │  • 减少每连接位数组的大小                                │  ││  │  • 提高缓存效率                                          │  ││  │  • 降低内存占用                                          │  ││  └─────────────────────────────────────────────────────────┘  ││                                                                ││  映射关系:                                                     ││  ┌─────────────────────────────────────────────────────────┐  ││  │  LocalIndex (0~4095)  ←→  NetRefIndex (0~65535)        │  ││  │                                                          │  ││  │  LocalToNetRefIndex[0] = 1234  (对象A)                  │  ││  │  LocalToNetRefIndex[1] = 5678  (对象B)                  │  ││  │  LocalToNetRefIndex[2] = 9012  (对象C)                  │  ││  │  ...                                                     │  ││  └─────────────────────────────────────────────────────────┘  ││                                                                ││  每连接的位数组只需要 4096 位 = 512 字节                       ││  而不是 65536 位 = 8192 字节                                   ││                                                                │└────────────────────────────────────────────────────────────────┘

        使用场景 🎮:

        • 私人物品只对拥有者可见

        • 团队专属对象

        • 动态改变对象的可见性

        • 依赖对象(Dependent Object)的过滤

        ⚠️ 注意:ConnectionFilter 不适合管理大量对象。对于静态的连接过滤需求,推荐使用 UReplicationSystem::SetConnectionFilter API。

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────┐
        │              🔌 连接过滤示例                                │
        ├────────────────────────────────────────────────────────────┤
        │                                                            │
        │  对象: [玩家A的背包]                                        │
        │                                                            │
        │  ┌─────────────┬─────────────┬─────────────┐              │
        │  │   玩家A     │   玩家B     │   玩家C     │              │
        │  │   连接      │   连接      │   连接      │              │
        │  ├─────────────┼─────────────┼─────────────┤              │
        │  │     ✅      │     ❌      │     ❌      │              │
        │  │   可以看    │   看不到    │   看不到    │              │
        │  └─────────────┴─────────────┴─────────────┘              │
        │                                                            │
        │  代码实现:                                                 │
        │  ConnectionFilter->SetReplicateToConnection(               │
        │      BackpackHandle, PlayerA_ConnectionId, Allow);         │
        │  ConnectionFilter->SetReplicateToConnection(               │
        │      BackpackHandle, PlayerB_ConnectionId, Disallow);      │
        │  ConnectionFilter->SetReplicateToConnection(               │
        │      BackpackHandle, PlayerC_ConnectionId, Disallow);      │
        │                                                            │
        └────────────────────────────────────────────────────────────┘

        🌐 5.4 空间过滤器详解 (GridFilter)

        空间过滤器是 Iris 中最重要也最复杂的过滤器,它基于对象的世界位置进行过滤。让我们深入源码,彻底理解它的工作原理。

        🏗️ GridFilter 类层次结构

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        // 抽象基类 - 定义网格过滤的核心逻辑UCLASS(abstract)
        class UNetObjectGridFilter : public UNetObjectFilter
        {
            // 网格划分、距离计算、帧计数等核心逻辑
        };
        
        // 基于状态数据的实现 - 从对象的复制状态中读取位置UCLASS()
        class UNetObjectGridWorldLocStateFilter : public UNetObjectGridFilter
        {
            // 通过 RepTag_WorldLocation 从状态缓冲区读取位置
        };
        
        // 基于 WorldLocations 的实现 - 从全局位置缓存读取位置UCLASS()
        class UNetObjectGridWorldLocFilter : public UNetObjectGridFilter
        {
            // 通过 FWorldLocations 缓存读取位置(更高效)
        };

        为什么有两种实现?

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🏗️ 两种 GridFilter 实现的区别                     │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  UNetObjectGridWorldLocStateFilter(状态读取模式):            │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  对象状态缓冲区                                          │  │
        │  │  ┌─────────────────────────────────────────────────┐    │  │
        │  │  │ [Health][Ammo][Position][Rotation][...]         │    │  │
        │  │  └───────────────────────↑─────────────────────────┘    │  │
        │  │                          │                              │  │
        │  │  通过 RepTag_WorldLocation 找到偏移量,直接读取          │  │
        │  │  ✅ 优点:不需要额外内存                                 │  │
        │  │  ❌ 缺点:每次读取需要计算偏移                           │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  UNetObjectGridWorldLocFilter(缓存读取模式):                 │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  FWorldLocations 全局缓存                                │  │
        │  │  ┌─────────────────────────────────────────────────┐    │  │
        │  │  │ ObjectIndex → { WorldLocation, CullDistance }   │    │  │
        │  │  └─────────────────────────────────────────────────┘    │  │
        │  │                                                          │  │
        │  │  直接通过索引查找,O(1) 访问                             │  │
        │  │  ✅ 优点:访问速度快,缓存友好                           │  │
        │  │  ❌ 缺点:需要额外维护缓存                               │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📐 网格划分算法

        GridFilter 将游戏世界划分成规则的网格,每个网格称为一个"单元格"(Cell)。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        // 网格坐标结构struct FCellCoord
        {
            int32 X;
            int32 Y;
            
            // 用于哈希表查找
            friend uint32 GetTypeHash(const FCellCoord& Coord)
            {
                return HashCombine(GetTypeHash(Coord.X), GetTypeHash(Coord.Y));
            }
            
            bool operator==(const FCellCoord& Other) const
            {
                return X == Other.X && Y == Other.Y;
            }
        };
        
        // 单元格包围盒 - 对象可能跨越多个单元格struct FCellBox
        {
            int32 MinX = 0;
            int32 MaxX = 0;
            int32 MinY = 0;
            int32 MaxY = 0;
        };
        
        // 将世界坐标转换为网格坐标void UNetObjectGridFilter::CalculateCellCoord(FCellCoord& OutCoord, const FVector& Pos){
            OutCoord.X = FPlatformMath::FloorToInt(Pos.X / Config->CellSizeX);
            OutCoord.Y = FPlatformMath::FloorToInt(Pos.Y / Config->CellSizeY);
        }

        网格划分示意图:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │                    🌐 网格划分原理                              │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  世界坐标系:                    网格坐标系:                     │
        │                                                                │
        │  Y ↑                           CellY ↑                         │
        │    │                                 │                         │
        │  60000 ─┼───────────────           2 ─┼─────┬─────┬─────       │
        │    │    │               →              │(0,2)│(1,2)│(2,2)      │
        │  40000 ─┼───────────────           1 ─┼─────┼─────┼─────       │
        │    │    │                              │(0,1)│(1,1)│(2,1)      │
        │  20000 ─┼───────────────           0 ─┼─────┼─────┼─────       │
        │    │    │                              │(0,0)│(1,0)│(2,0)      │
        │    0 ───┼───┬───┬───→ X            ───┴─────┴─────┴─────→ CellX│
        │         0  20000 40000                  0     1     2          │
        │                                                                │
        │  CellSize = 20000 (200米)                                      │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📦 对象的网格归属

        一个对象可能因为剔除距离较大而跨越多个网格单元。GridFilter 使用 FCellBox 来表示对象所属的所有单元格:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.cpp
        
        void UNetObjectGridFilter::CalculateCellBox(
            const FPerObjectInfo& PerObjectInfo, 
            FCellBox& OutCellBox){
            const double CullDistance = PerObjectInfo.GetCullDistance();
            const FVector Position = PerObjectInfo.Position;
            
            // 计算对象的 AABB 包围盒
            FVector MinPosition = Position - CullDistance;
            FVector MaxPosition = Position + CullDistance;
            
            // 转换为网格坐标
            const int64 MinX = FPlatformMath::FloorToInt(MinPosition.X / Config->CellSizeX);
            const int64 MinY = FPlatformMath::FloorToInt(MinPosition.Y / Config->CellSizeY);
            const int64 MaxX = FPlatformMath::FloorToInt(MaxPosition.X / Config->CellSizeX);
            const int64 MaxY = FPlatformMath::FloorToInt(MaxPosition.Y / Config->CellSizeY);
            
            OutCellBox.MinX = static_cast<int32>(MinX);
            OutCellBox.MinY = static_cast<int32>(MinY);
            OutCellBox.MaxX = static_cast<int32>(MaxX);
            OutCellBox.MaxY = static_cast<int32>(MaxY);
        }

        对象跨越多个单元格的示例:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              📦 对象的网格归属                                  │├────────────────────────────────────────────────────────────────┤│                                                                ││  小剔除距离对象(如装饰物,50m):                              ││  ┌─────┬─────┬─────┐                                          ││  │     │     │     │                                          ││  ├─────┼─────┼─────┤   对象位置: (25000, 25000)               ││  │     │ 🌳  │     │   CellBox: (1,1) → (1,1)                 ││  ├─────┼─────┼─────┤   只在一个单元格中                        ││  │     │     │     │                                          ││  └─────┴─────┴─────┘                                          ││                                                                ││  大剔除距离对象(如玩家,200m):                               ││  ┌─────┬─────┬─────┐                                          ││  │  ┌──┼─────┼──┐  │   对象位置: (30000, 30000)               ││  ├──┼──┼─────┼──┼──┤   剔除距离: 20000                        ││  │  │  │ 👤  │  │  │   CellBox: (0,0) → (2,2)                 ││  ├──┼──┼─────┼──┼──┤   跨越 9 个单元格!                       ││  │  └──┼─────┼──┘  │                                          ││  └─────┴─────┴─────┘                                          ││                                                                ││  ⚠️ 大剔除距离对象会显著增加内存和计算开销                     ││     Config->MaxCullDistance 可以限制最大剔除距离               ││                                                                │└────────────────────────────────────────────────────────────────┘

        🗃️ 内部数据结构

        GridFilter 使用多个数据结构来高效管理对象:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        class UNetObjectGridFilter : public UNetObjectFilter
        {
        protected:
            // 每个对象的详细信息
            struct FPerObjectInfo
            {
                FVector Position = FVector::ZeroVector;  // 世界位置
                FCellBox CellBox = {};                   // 所属单元格范围
                uint32 ObjectIndex = 0U;                 // 对象索引
                uint16 FrameCountBeforeCulling = 0U;     // 剔除前帧数
                
            private:
                float CullDistance = 0.0f;               // 剔除距离
                float CullDistanceSq = 0.0f;             // 剔除距离平方(优化)
                
            public:
                float GetCullDistance() const { return CullDistance; }
                float GetCullDistanceSq() const { return CullDistanceSq; }
                
                void SetCullDistance(float Distance)
                {
                    CullDistance = Distance;
                    CullDistanceSq = Distance * Distance;  // 预计算平方
                }
            };
            
            // 每个单元格存储的对象列表
            struct FCellObjects
            {
                TArray<uint32> ObjectIndices;  // 该单元格中的对象索引
            };
            
            // 每个连接的过滤状态
            struct FPerConnectionInfo
            {
                // 最近在范围内的对象及其帧计数器
                TMap<uint32, uint16> RecentObjectFrameCount;
                
                // 最近访问过的单元格(用于视图位置相关性)
                TArray<FCellAndTimestamp> RecentCells;
            };
            
            // 核心数据结构
            TMap<FCellCoord, FCellObjects> Cells;              // 网格 → 对象列表
            TChunkedArray<FPerObjectInfo> ObjectInfos;         // 对象详细信息
            TArray<FPerConnectionInfo> PerConnectionInfos;     // 每连接状态
            FNetBitArray AssignedObjectInfoIndices;            // 已分配的槽位
            
            TStrongObjectPtr<UNetObjectGridFilterConfig> Config;  // 配置
        };

        数据结构关系图:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🗃️ GridFilter 数据结构关系                        │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  Cells (TMap<FCellCoord, FCellObjects>)                       │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  (0,0) → [Obj1, Obj5, Obj12]                            │  │
        │  │  (0,1) → [Obj3, Obj7]                                   │  │
        │  │  (1,0) → [Obj2, Obj5, Obj8]  ← Obj5 跨越多个单元格      │  │
        │  │  (1,1) → [Obj4, Obj5, Obj9]                             │  │
        │  │  ...                                                     │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                              │                                 │
        │                              ▼                                 │
        │  ObjectInfos (TChunkedArray<FPerObjectInfo>)                  │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  [0] Obj1: Pos=(1000,2000), CullDist=15000, CellBox=... │  │
        │  │  [1] Obj2: Pos=(5000,3000), CullDist=10000, CellBox=... │  │
        │  │  [2] Obj3: Pos=(8000,1000), CullDist=20000, CellBox=... │  │
        │  │  ...                                                     │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                              │                                 │
        │                              ▼                                 │
        │  PerConnectionInfos (每个玩家连接)                             │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  Connection[1]:                                          │  │
        │  │    RecentObjectFrameCount: {Obj1→4, Obj3→2, Obj5→4}     │  │
        │  │    RecentCells: [(0,0,Frame100), (0,1,Frame100)]        │  │
        │  │  Connection[2]:                                          │  │
        │  │    RecentObjectFrameCount: {Obj2→3, Obj4→4}             │  │
        │  │    RecentCells: [(1,1,Frame100)]                        │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        👁️ 视图位置更新

        每个玩家都有一个或多个"视图位置"(View Position),通常是摄像机位置。GridFilter 需要知道每个玩家在看哪里。

        CPP
        // 视图信息结构struct FReplicationView
        {
            FVector Position;      // 视图位置
            FVector Direction;     // 视图方向
            float FieldOfView;     // 视野角度
        };
        
        // 更新连接的视图位置void UpdateViewPosition(uint32 ConnectionId, const TArray<FReplicationView>& Views);

        多视图支持(如分屏游戏):

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │                  👁️ 多视图支持                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  单人游戏:                      分屏游戏:                       │
        │  ┌─────────────────┐           ┌────────┬────────┐            │
        │  │                 │           │ 视图1  │ 视图2  │            │
        │  │    👁️ 视图1     │           │  👁️    │   👁️   │            │
        │  │                 │           │        │        │            │
        │  └─────────────────┘           └────────┴────────┘            │
        │                                                                │
        │  同步范围 = 视图1附近          同步范围 = 视图1 ∪ 视图2 附近    │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📏 剔除距离配置

        剔除距离决定了对象在多远时会被过滤掉。Iris 支持多种剔除距离配置:

        CPP
        // 配置预设 - 为不同类型的对象设置不同的剔除距离USTRUCT()
        struct FNetObjectGridFilterProfile
        {
            UPROPERTY(Config)
            FName ProfileName;                    // 预设名称
            
            UPROPERTY(Config)
            float CullDistance = 15000.0f;        // 剔除距离
            
            UPROPERTY(Config)
            uint16 FrameCountBeforeCulling = 4;   // 剔除前帧数
        };

        不同对象的剔除距离示例:

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────────┐
        │                     📏 剔除距离配置示例                              │
        ├─────────────────────────────────────────────────────────────────────┤
        │                                                                     │
        │  对象类型          剔除距离        原因                              │
        │  ──────────────────────────────────────────────────────────────────│
        │  👤 其他玩家       20000 (200m)   玩家很重要,需要较大范围           │
        │  🧟 普通怪物       15000 (150m)   中等重要性                         │
        │  🎁 掉落物品       10000 (100m)   只有靠近才需要看到                 │
        │  🌳 装饰物         5000  (50m)    纯视觉效果,可以晚点同步           │
        │  💥 特效粒子       3000  (30m)    很近才能看清                       │
        │                                                                     │
        │  剔除范围示意图(实际使用 AABB 方形包围盒):                        │
        │                                                                     │
        │    ┌─────────────────────────────────────────────────┐              │
        │    │ 👤 200m                                         │              │
        │    │   ┌───────────────────────────────────────┐     │              │
        │    │   │ 🧟 150m                               │     │              │
        │    │   │   ┌───────────────────────────────┐   │     │              │
        │    │   │   │ 🎁 100m                       │   │     │              │
        │    │   │   │   ┌───────────────────────┐   │   │     │              │
        │    │   │   │   │ 🌳 50m                │   │   │     │              │
        │    │   │   │   │                       │   │   │     │              │
        │    │   │   │   │         👤            │   │   │     │              │
        │    │   │   │   │        (你)           │   │   │     │              │
        │    │   │   │   │                       │   │   │     │              │
        │    │   │   │   └───────────────────────┘   │   │     │              │
        │    │   │   │                               │   │     │              │
        │    │   │   └───────────────────────────────┘   │     │              │
        │    │   │                                       │     │              │
        │    │   └───────────────────────────────────────┘     │              │
        │    │                                                 │              │
        │    └─────────────────────────────────────────────────┘              │
        │                                                                     │
        │  📝 源码实现 (NetObjectGridFilter.cpp:498-499):                     │
        │     FVector MinPosition = Position - CullDistance;                  │
        │     FVector MaxPosition = Position + CullDistance;                  │
        │                                                                     │
        └─────────────────────────────────────────────────────────────────────┘

        ⚡ 性能优化策略

        GridFilter 采用了多种优化策略来保证高性能,这些优化对于支持大规模多人游戏至关重要。

        1️⃣ 空间哈希(Spatial Hashing)

        核心优化:不需要遍历所有对象,只检查相关网格中的对象。

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🚀 空间哈希性能对比                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  传统暴力方法:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  for (每个对象 in 所有10000个对象)                       │  │
        │  │      for (每个视图 in 玩家视图)                          │  │
        │  │          计算距离,判断是否在范围内                       │  │
        │  │                                                          │  │
        │  │  时间复杂度: O(N × V) = O(10000 × 2) = 20000次计算       │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  网格哈希方法:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  相关单元格 = 玩家所在单元格 + 相邻单元格(约9个)        │  │
        │  │  for (每个单元格 in 相关单元格)                          │  │
        │  │      对象列表 = Cells[单元格]  // O(1) 哈希查找          │  │
        │  │      for (每个对象 in 对象列表)                          │  │
        │  │          精确距离检查(可选)                             │  │
        │  │                                                          │  │
        │  │  时间复杂度: O(K) 其中 K = 相关单元格中的对象数          │  │
        │  │  假设每个单元格平均50个对象: O(9 × 50) = 450次计算       │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  性能提升: 20000 / 450 ≈ 44倍!                               │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        2️⃣ 帧计数延迟剔除(Hysteresis)

        对象不会立即被剔除,而是等待几帧。这防止了对象在边界处频繁闪烁,同时减少了状态切换的开销。

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.cpp
        
        // Filter 方法中的帧计数器处理(第 232-243 行)for (TMap<uint32, uint16>::TIterator It = ConnectionInfo.RecentObjectFrameCount.CreateIterator(); It; ++It)
        {
            if (It->Value > 0)
            {
                It->Value--;                    // 计数器递减
                AllowedObjects.SetBit(It->Key); // 仍然允许复制
            }
            else
            {
                It.RemoveCurrent();             // 计数器归零,真正移除
            }
        }

        帧计数器的工作流程:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              ⏱️ 帧计数器状态机                                  │├────────────────────────────────────────────────────────────────┤│                                                                ││  对象进入范围:                                                  ││  ┌─────────────────────────────────────────────────────────┐  ││  │  RecentObjectFrameCount[ObjectIndex] = FrameCountBeforeCulling ││  │  AllowedObjects.SetBit(ObjectIndex)                      │  ││  └─────────────────────────────────────────────────────────┘  ││                    │                                           ││                    ▼                                           ││  每帧检查:         ┌──────────────────────────────────────┐   ││                    │  对象仍在范围内?                     │   ││                    └──────────────────────────────────────┘   ││                    │ Yes                    │ No              ││                    ▼                        ▼                 ││  ┌─────────────────────────┐  ┌─────────────────────────┐    ││  │ 重置计数器为最大值       │  │ 计数器 > 0?             │    ││  │ 保持 AllowedObjects 位  │  └─────────────────────────┘    ││  └─────────────────────────┘     │ Yes          │ No         ││                                  ▼              ▼             ││                    ┌─────────────────┐  ┌─────────────────┐  ││                    │ 计数器--        │  │ 移除对象        │  ││                    │ 保持允许状态    │  │ 清除允许位      │  ││                    └─────────────────┘  └─────────────────┘  ││                                                                │└────────────────────────────────────────────────────────────────┘

        3️⃣ 精确距离 vs 网格距离

        GridFilter 提供两种距离计算模式,可以根据性能需求选择:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.cpp
        
        void UNetObjectGridFilter::Filter(FNetObjectFilteringParams& Params){
            if (Config->bUseExactCullDistance)
            {
                // 精确模式:使用欧几里得距离
                for (uint32 ObjectIndex : Objects->ObjectIndices)
                {
                    const FPerObjectInfo& PerObjectInfo = ObjectInfos[...];
                    
                    for (const FReplicationView::FView& View : Params.View.Views)
                    {
                        // 使用距离平方避免开方运算
                        const double DistSq = PerObjectInfo.GetCullDistanceSq();
                        const double ObjectToViewDistSq = FVector::DistSquared(
                            PerObjectInfo.Position, 
                            View.Pos
                        );
                        
                        if (ObjectToViewDistSq <= DistSq)
                        {
                            ConnectionInfo.RecentObjectFrameCount.Add(
                                ObjectIndex, 
                                PerObjectInfo.FrameCountBeforeCulling
                            );
                            break;
                        }
                    }
                }
            }
            else
            {
                // 网格模式:单元格内所有对象都允许
                for (uint32 ObjectIndex : Objects->ObjectIndices)
                {
                    AllowedObjects.SetBit(ObjectIndex);
                }
            }
        }

        两种模式的对比:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📊 精确模式 vs 网格模式                            │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  精确模式 (bUseExactCullDistance = true):                      │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  ✅ 优点:                                                │  │
        │  │     • 精确的距离判断,不会同步超出范围的对象             │  │
        │  │     • 带宽利用更高效                                     │  │
        │  │  ❌ 缺点:                                                │  │
        │  │     • 每个对象都需要计算距离(虽然用平方优化)           │  │
        │  │     • CPU 开销较大                                       │  │
        │  │  📍 适用场景: 带宽受限、对象数量适中                     │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  网格模式 (bUseExactCullDistance = false):                     │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  ✅ 优点:                                                │  │
        │  │     • 极快的过滤速度,只需检查单元格归属                 │  │
        │  │     • CPU 开销极小                                       │  │
        │  │  ❌ 缺点:                                                │  │
        │  │     • 可能同步单元格内超出剔除距离的对象                 │  │
        │  │     • 带宽利用率略低                                     │  │
        │  │  📍 适用场景: CPU 受限、对象数量巨大                     │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        4️⃣ 距离平方优化

        避免昂贵的开方运算,使用距离平方进行比较:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        struct FPerObjectInfo
        {
        private:
            float CullDistance = 0.0f;
            float CullDistanceSq = 0.0f;  // 预计算的平方值
            
        public:
            void SetCullDistance(float Distance)
            {
                CullDistance = Distance;
                CullDistanceSq = Distance * Distance;  // 只在设置时计算一次
            }
            
            float GetCullDistanceSq() const { return CullDistanceSq; }
        };
        
        // 使用时直接比较平方值,避免开方const double ObjectToViewDistSq = FVector::DistSquared(ObjPos, ViewPos);
        if (ObjectToViewDistSq <= PerObjectInfo.GetCullDistanceSq())
        {
            // 在范围内
        }

        5️⃣ 分块数组(Chunked Array)

        使用分块数组存储对象信息,避免大数组重新分配:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        enum : unsigned
        {
            ObjectInfosChunkSize = 64 * 1024,  // 64KB 每块
        };
        
        // 分块存储,避免单个大数组
        TChunkedArray<FPerObjectInfo, ObjectInfosChunkSize> ObjectInfos;

        6️⃣ 视图位置相关性帧数

        玩家移动时,之前所在的单元格不会立即失效:

        CPP
        // 📍 配置:ViewPosRelevancyFrameCount = 2
        
        // 玩家从单元格 (1,1) 移动到 (1,2)// 帧 N:   当前单元格 (1,2),之前单元格 (1,1) 仍然相关// 帧 N+1: 当前单元格 (1,2),之前单元格 (1,1) 仍然相关// 帧 N+2: 当前单元格 (1,2),之前单元格 (1,1) 不再相关
        
        // 这避免了玩家在单元格边界来回移动时的频繁重新计算

        🏢 5.5 组过滤 (Group Filtering)

        组过滤是一种批量管理对象可见性的机制,特别适合关卡流送(Level Streaming)场景。通过将对象分组,可以一次性控制大量对象的复制状态。

        🏗️ 组系统核心数据结构

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGroups.h
        
        // 组特性枚举 - 定义组的过滤行为enum class ENetObjectGroupTraits : uint32
        {
            None                 = 0x0000,
            IsExclusionFiltering = 0x0001,  // 排除过滤:组内对象被排除
            IsInclusionFiltering = 0x0002,  // 包含过滤:组内对象被强制包含
        };
        ENUM_CLASS_FLAGS(ENetObjectGroupTraits);
        
        // 组数据结构struct FNetObjectGroup
        {
            TArray<FInternalNetRefIndex> Members;  // 组成员列表
            FName GroupName;                        // 组名称
            uint32 GroupId = 0U;                    // 唯一标识符
            ENetObjectGroupTraits Traits = ENetObjectGroupTraits::None;  // 组特性
        };
        
        // 组句柄 - 用于安全地引用组struct FNetObjectGroupHandle
        {
            FGroupIndexType Index;    // 组在数组中的索引
            FGroupIndexType Epoch;    // 版本号(用于检测过期句柄)
            uint32 UniqueId;          // 唯一ID(防止重用冲突)
        };

        组句柄的安全性设计:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔒 组句柄安全性设计                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  问题:组被销毁后,旧的句柄可能被误用                           │
        │                                                                │
        │  解决方案:三重验证机制                                         │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  1. Index: 组在数组中的位置                              │  │
        │  │  2. Epoch: 复制系统的版本号,重启后递增                  │  │
        │  │  3. UniqueId: 每个组的唯一ID,永不重复                   │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  验证逻辑:                                                     │
        │  bool IsValidGroup(FNetObjectGroupHandle Handle) const         │
        │  {                                                             │
        │      // 1. 检查句柄是否有效                                    │
        │      // 2. 检查版本号是否匹配                                  │
        │      // 3. 检查索引是否存在                                    │
        │      // 4. 检查 UniqueId 是否匹配                              │
        │      return Handle.IsValid() &&                                │
        │             Handle.Epoch == CurrentEpoch &&                    │
        │             Groups.IsValidIndex(Handle.Index) &&               │
        │             Groups[Handle.Index].GroupId == Handle.UniqueId;   │
        │  }                                                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📦 组的创建与销毁

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGroups.cpp
        
        class FNetObjectGroups
        {
        public:
            // 创建一个新组
            FNetObjectGroupHandle CreateGroup(FName GroupName);
            
            // 销毁组(会自动清空成员)
            void DestroyGroup(FNetObjectGroupHandle GroupHandle);
            
            // 清空组(保留组,移除所有成员)
            void ClearGroup(FNetObjectGroupHandle GroupHandle);
            
            // 添加对象到组
            void AddToGroup(FNetObjectGroupHandle GroupHandle, FInternalNetRefIndex InternalIndex);
            
            // 从组移除对象
            void RemoveFromGroup(FNetObjectGroupHandle GroupHandle, FInternalNetRefIndex InternalIndex);
            
            // 查询对象所属的所有组
            void GetGroupHandlesOfNetObject(FInternalNetRefIndex InternalIndex, 
                                             TArray<FNetObjectGroupHandle>& OutHandles) const;
        };

        CreateGroup 实现详解:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGroups.cpp
        
        FNetObjectGroupHandle FNetObjectGroups::CreateGroup(FName InGroupName){
            // 1. 检查是否达到最大组数限制
            const bool bCanCreateGroup = (uint32)Groups.Num() < MaxGroupCount;
            if (!bCanCreateGroup)
            {
                UE_LOG(LogIrisGroup, Warning, TEXT("Maximum allowed groups allocated: %u"), MaxGroupCount);
                return FNetObjectGroupHandle();
            }
            
            // 2. 自动生成名称(如果未提供)
            if (InGroupName == NAME_None)
            {
                InGroupName = FName(TEXT("NetObjectGroup"), AutogeneratedGroupNameId++);
            }
            
            // 3. 验证名称唯一性
            const FNetObjectGroupHandle ExistingGroup = FindGroupHandle(InGroupName);
            if (ExistingGroup.IsValid())
            {
                UE_LOG(LogIrisGroup, Warning, TEXT("Group name %s is already registered"), *InGroupName.ToString());
                return FNetObjectGroupHandle();
            }
            
            // 4. 分配唯一ID并创建组
            const uint32 NewGroupId = NextGroupUniqueId++;
            const uint32 Index = Groups.Emplace(FNetObjectGroup{
                .GroupName = InGroupName, 
                .GroupId = NewGroupId
            });
            
            // 5. 构建并返回句柄
            return FNetObjectGroupHandle(Index, CurrentEpoch, NewGroupId);
        }

        组的生命周期:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │                    📦 组的生命周期                              │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  CreateGroup("Level_Forest")                                   │
        │         │                                                      │
        │         ▼                                                      │
        │  ┌─────────────────────────────────────┐                      │
        │  │  组: Level_Forest                    │                      │
        │  │  成员: []                            │                      │
        │  │  特性: None                          │                      │
        │  └─────────────────────────────────────┘                      │
        │         │                                                      │
        │         │ AddToGroup(树1, 树2, 怪物1...)                       │
        │         ▼                                                      │
        │  ┌─────────────────────────────────────┐                      │
        │  │  组: Level_Forest                    │                      │
        │  │  成员: [🌳树1, 🌳树2, 🧟怪物1...]    │                      │
        │  │  特性: None                          │                      │
        │  └─────────────────────────────────────┘                      │
        │         │                                                      │
        │         │ AddExclusionFilterTrait()                            │
        │         ▼                                                      │
        │  ┌─────────────────────────────────────┐                      │
        │  │  组: Level_Forest                    │                      │
        │  │  成员: [🌳树1, 🌳树2, 🧟怪物1...]    │                      │
        │  │  特性: IsExclusionFiltering ← 变成排除组                   │
        │  └─────────────────────────────────────┘                      │
        │         │                                                      │
        │         │ 玩家离开森林区域                                      │
        │         │ DestroyGroup() 或 ClearGroup()                       │
        │         ▼                                                      │
        │  ┌─────────────────────────────────────┐                      │
        │  │  组已销毁或清空                       │                      │
        │  └─────────────────────────────────────┘                      │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🚫 包含组 vs ✅ 排除组

        Iris 支持两种类型的组过滤,它们的行为完全相反:

        类型

        效果

        使用场景

        排除组 (Exclusion)

        组内对象不会被同步

        隐藏特定区域的对象

        包含组 (Inclusion)

        组内对象强制被同步(覆盖其他过滤)

        确保重要对象可见

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGroups.cpp
        
        // 设置组为排除组void FNetObjectGroups::AddExclusionFilterTrait(FNetObjectGroupHandle GroupHandle){
            if (FNetObjectGroup* Group = GetGroup(GroupHandle))
            {
                // 只有非过滤组才能添加特性(不能同时是排除和包含)
                if (!IsFilterGroup(*Group))
                {
                    Group->Traits |= ENetObjectGroupTraits::IsExclusionFiltering;
                    
                    // 标记所有成员为可过滤
                    for (FInternalNetRefIndex MemberIndex : Group->Members)
                    {
                        GroupFilteredOutObjects.SetBit(MemberIndex);
                    }
                }
            }
        }
        
        // 设置组为包含组void FNetObjectGroups::AddInclusionFilterTrait(FNetObjectGroupHandle GroupHandle){
            if (FNetObjectGroup* Group = GetGroup(GroupHandle))
            {
                // 不能同时是排除和包含
                if (!IsFilterGroup(*Group))
                {
                    Group->Traits |= ENetObjectGroupTraits::IsInclusionFiltering;
                }
            }
        }
        
        // 查询组类型bool FNetObjectGroups::IsExclusionFilterGroup(FNetObjectGroupHandle GroupHandle) const;
        bool FNetObjectGroups::IsInclusionFilterGroup(FNetObjectGroupHandle GroupHandle) const;

        排除组 vs 包含组示意图:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🚫 排除组 vs ✅ 包含组                             │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  场景:玩家在城镇,森林关卡未加载                               │
        │                                                                │
        │  ┌──────────────────┐    ┌──────────────────┐                 │
        │  │  🏘️ 城镇区域      │    │  🌲 森林区域      │                 │
        │  │  (当前区域)       │    │  (未加载)         │                 │
        │  │                  │    │                  │                 │
        │  │  [商人][铁匠]    │    │  [树][怪物]      │                 │
        │  │  [玩家][NPC]     │    │  [宝箱][Boss]    │                 │
        │  │                  │    │                  │                 │
        │  │  ✅ 正常同步      │    │  🚫 排除组过滤    │                 │
        │  └──────────────────┘    └──────────────────┘                 │
        │                                                                │
        │  ─────────────────────────────────────────────────────────────│
        │                                                                │
        │  场景:Boss 战斗,确保 Boss 始终可见                            │
        │                                                                │
        │  即使 Boss 距离很远,使用包含组强制同步:                        │
        │                                                                │
        │  ┌─────────────────────────────────────────┐                  │
        │  │  正常过滤结果: [玩家附近的对象...]        │                  │
        │  │       +                                  │                  │
        │  │  包含组强制添加: [👹Boss]                 │                  │
        │  │       =                                  │                  │
        │  │  最终结果: [玩家附近对象...] + [👹Boss]   │                  │
        │  └─────────────────────────────────────────┘                  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📊 对象的组成员关系追踪

        每个对象可以属于多个组,系统需要高效地追踪这种多对多关系:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/NetObjectGroups.h
        
        class FNetObjectGroups
        {
        private:
            // 每个对象的组成员关系
            struct FNetObjectGroupMembership
            {
            private:
                enum { NumInlinedGroupHandles = 2 };  // 内联优化:大多数对象只属于1-2个组
                TArray<FGroupIndexType, TInlineAllocator<NumInlinedGroupHandles>> GroupIndexes;
                
            public:
                bool ContainsMembership(FNetObjectGroupHandle InGroupHandle) const;
                void AddMembership(FNetObjectGroupHandle InGroupHandle);
                void RemoveMembership(FNetObjectGroupHandle InGroupHandle);
                int32 NumMemberships() const;
            };
            
            // 所有对象的组成员关系数组
            TArray<FNetObjectGroupMembership> GroupMemberships;
            
            // 属于任何过滤组的对象位图(用于快速查询)
            FNetBitArray GroupFilteredOutObjects;
        };

        组成员关系的内存优化:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              📊 组成员关系内存优化                              │├────────────────────────────────────────────────────────────────┤│                                                                ││  观察:大多数对象只属于 1-2 个组                                ││                                                                ││  优化策略:TInlineAllocator<2>                                 ││  ┌─────────────────────────────────────────────────────────┐  ││  │  FNetObjectGroupMembership 结构:                        │  ││  │                                                          │  ││  │  ┌────────────────────────────────────────────────────┐ │  ││  │  │  内联存储 (2 个槽位,无堆分配)                      │ │  ││  │  │  [GroupIndex1] [GroupIndex2]                       │ │  ││  │  └────────────────────────────────────────────────────┘ │  ││  │                                                          │  ││  │  如果对象属于 >2 个组,才会触发堆分配                    │  ││  └─────────────────────────────────────────────────────────┘  ││                                                                ││  控制台变量保护:                                               ││  CVarEnsureIfNumGroupMembershipsExceeds = 128                  ││  如果对象属于超过 128 个组,会触发警告(可能是 bug)            ││                                                                │└────────────────────────────────────────────────────────────────┘

        🔌 连接级别的组状态控制

        组过滤可以针对不同的连接设置不同的状态:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.h
        
        class FReplicationFiltering
        {
        public:
            // 为所有连接设置组状态
            void SetGroupFilterStatus(
                FNetObjectGroupHandle GroupHandle, 
                ENetFilterStatus ReplicationStatus
            );
            
            // 为特定连接设置组状态
            void SetGroupFilterStatus(
                FNetObjectGroupHandle GroupHandle, 
                uint32 ConnectionId, 
                ENetFilterStatus ReplicationStatus
            );
            
            // 添加/移除过滤组
            bool AddExclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
            bool RemoveExclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
            bool AddInclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
            bool RemoveInclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
        };

        应用示例:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔌 连接级别组状态控制                              │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  场景:两个玩家在不同区域                                       │
        │                                                                │
        │  玩家A 在森林    →  森林组: Allow                              │
        │  玩家B 在城镇    →  森林组: Disallow                           │
        │                                                                │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │                    森林组对象                            │  │
        │  │              [🌳树1] [🌳树2] [🧟怪物]                    │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                          │                                     │
        │            ┌─────────────┴─────────────┐                      │
        │            ▼                           ▼                      │
        │  ┌─────────────────┐         ┌─────────────────┐             │
        │  │    玩家A连接     │         │    玩家B连接     │             │
        │  │   状态: Allow   │         │  状态: Disallow │             │
        │  │                 │         │                 │             │
        │  │  收到: 🌳🌳🧟    │         │  收到: (无)     │             │
        │  └─────────────────┘         └─────────────────┘             │
        │                                                                │
        │  代码实现:                                                     │
        │  // 玩家A进入森林                                              │
        │  ReplicationFiltering->SetGroupFilterStatus(                   │
        │      ForestGroup, PlayerA_ConnectionId, Allow);                │
        │                                                                │
        │  // 玩家B离开森林                                              │
        │  ReplicationFiltering->SetGroupFilterStatus(                   │
        │      ForestGroup, PlayerB_ConnectionId, Disallow);             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🗺️ 关卡流送中的应用

        组过滤最典型的应用场景就是关卡流送(Level Streaming):

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🗺️ 关卡流送与组过滤                               │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  游戏世界划分为多个流送关卡:                                   │
        │                                                                │
        │  ┌─────────┬─────────┬─────────┐                              │
        │  │ Level_A │ Level_B │ Level_C │                              │
        │  │ (城镇)  │ (森林)  │ (沙漠)  │                              │
        │  ├─────────┼─────────┼─────────┤                              │
        │  │ Level_D │ Level_E │ Level_F │                              │
        │  │ (雪山)  │ (沼泽)  │ (火山)  │                              │
        │  └─────────┴─────────┴─────────┘                              │
        │                                                                │
        │  每个关卡对应一个组:                                           │
        │  - Group_LevelA, Group_LevelB, Group_LevelC...                │
        │                                                                │
        │  玩家加载关卡时:                                               │
        │  1. 客户端请求加载 Level_B                                     │
        │  2. 服务器设置 Group_LevelB 状态为 Allow                       │
        │  3. Level_B 中的对象开始同步给该玩家                           │
        │                                                                │
        │  玩家卸载关卡时:                                               │
        │  1. 客户端请求卸载 Level_A                                     │
        │  2. 服务器设置 Group_LevelA 状态为 Disallow                    │
        │  3. Level_A 中的对象停止同步给该玩家                           │
        │                                                                │
        │  实现代码示例:                                                 │
        │  void OnLevelLoaded(ULevel* Level, uint32 ConnectionId)        │
        │  {                                                             │
        │      FNetObjectGroupHandle Group = GetGroupForLevel(Level);    │
        │      ReplicationFiltering->SetGroupFilterStatus(               │
        │          Group, ConnectionId, ENetFilterStatus::Allow);        │
        │  }                                                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        ⚙️ 5.6 过滤器配置

        📄 核心配置类

        Iris 的过滤系统通过多个配置类进行控制,这些配置通常在 DefaultEngine.ini 或者 BaseEngine.ini 中设置。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/ReplicationFilteringConfig.h
        
        // 滞后配置预设 - 为不同类型的对象设置不同的滞后帧数USTRUCT()
        struct FObjectScopeHysteresisProfile
        {
            GENERATED_BODY()
            
            // 配置预设名称(用于在类配置中引用)
            UPROPERTY()
            FName FilterProfileName;
            
            // 对象被过滤后保持在范围内的帧数
            UPROPERTY()
            uint8 HysteresisFrameCount = 0;
        };
        
        // 主过滤配置类UCLASS(transient, config = Engine)
        class UReplicationFilteringConfig final : public UObject
        {
            GENERATED_BODY()
            
        private:
            // 是否启用对象范围滞后
            // 启用后,被动态过滤的对象不会立即移出范围
            UPROPERTY(Config)
            bool bEnableObjectScopeHysteresis = true;
            
            // 默认滞后帧数(可被预设覆盖)
            UPROPERTY(Config)
            uint8 DefaultHysteresisFrameCount = 0;
            
            // 连接更新节流
            // 值为 N 表示每帧只更新 1/N 的连接
            // 例如:值为 4 表示每帧更新 25% 的连接
            // 范围:1-128,值越大性能越好但响应越慢
            UPROPERTY(Config)
            uint8 HysteresisUpdateConnectionThrottling = 1;
            
            // 滞后配置预设列表
            UPROPERTY(Config)
            TArray<FObjectScopeHysteresisProfile> HysteresisProfiles;
            
        public:
            bool IsObjectScopeHysteresisEnabled() const { return bEnableObjectScopeHysteresis; }
            uint8 GetDefaultHysteresisFrameCount() const { return DefaultHysteresisFrameCount; }
            uint8 GetHysteresisUpdateConnectionThrottling() const 
            { 
                return FMath::Clamp<uint8>(HysteresisUpdateConnectionThrottling, 1U, 128U); 
            }
        };

        📄 配置文件格式

        过滤器通过配置文件进行设置,通常在 DefaultEngine.ini 中:

        INI
        ; 📍 Config/DefaultEngine.ini
        
        ; ═══════════════════════════════════════════════════════════════; 主过滤配置; ═══════════════════════════════════════════════════════════════[/Script/IrisCore.ReplicationFilteringConfig]; 启用对象范围滞后(防止边界闪烁)bEnableObjectScopeHysteresis=true
        
        ; 默认滞后帧数DefaultHysteresisFrameCount=4
        
        ; 连接节流更新(1=每帧更新所有连接,4=每帧更新25%连接)HysteresisUpdateConnectionThrottling=1
        
        ; 清除默认预设
        !HysteresisProfiles=ClearArray
        
        ; 添加滞后配置预设
        +HysteresisProfiles=(FilterProfileName="Default", HysteresisFrameCount=4)
        +HysteresisProfiles=(FilterProfileName="Important", HysteresisFrameCount=8)
        +HysteresisProfiles=(FilterProfileName="Decoration", HysteresisFrameCount=2)
        +HysteresisProfiles=(FilterProfileName="Pawn", HysteresisFrameCount=30)

        连接节流的工作原理:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              ⚡ 连接节流更新机制                                │├────────────────────────────────────────────────────────────────┤│                                                                ││  HysteresisUpdateConnectionThrottling = 4 时:                 ││                                                                ││  帧 1: 更新连接 [1, 5, 9, 13, ...]   (索引 % 4 == 1)          ││  帧 2: 更新连接 [2, 6, 10, 14, ...]  (索引 % 4 == 2)          ││  帧 3: 更新连接 [3, 7, 11, 15, ...]  (索引 % 4 == 3)          ││  帧 4: 更新连接 [0, 4, 8, 12, ...]   (索引 % 4 == 0)          ││  帧 5: 更新连接 [1, 5, 9, 13, ...]   (循环)                   ││                                                                ││  优点:减少每帧的计算量,适合大量连接的场景                     ││  缺点:对象可能多停留 N-1 帧才被真正移出范围                    ││                                                                ││  ⚠️ 建议保持较低的值(1-4),过高会导致响应延迟                ││                                                                │└────────────────────────────────────────────────────────────────┘

        🏷️ 类级别过滤器配置

        可以为不同的类配置不同的过滤器和预设:

        INI
        ; 📍 Config/DefaultEngine.ini
        
        ; ═══════════════════════════════════════════════════════════════; 对象复制桥配置 - 为不同类指定过滤器; ═══════════════════════════════════════════════════════════════[/Script/IrisCore.ObjectReplicationBridgeConfig]
        
        ; 清除默认配置
        !FilterConfigs=ClearArray
        
        ; 为 Pawn 类配置空间过滤器,使用 Pawn 预设(30帧滞后)
        +FilterConfigs=(ClassName="/Script/Engine.Pawn", DynamicFilterName="Spatial", FilterProfile="Pawn")
        
        ; 为重要 Actor 禁用动态过滤
        +FilterConfigs=(ClassName="/Script/MyGame.ImportantActor", DynamicFilterName="Nop", FilterProfile="")
        
        ; 为秘密对象使用连接过滤
        +FilterConfigs=(ClassName="/Script/MyGame.SecretActor", DynamicFilterName="Connection", FilterProfile="")
        
        ; 为装饰物使用较短的滞后
        +FilterConfigs=(ClassName="/Script/MyGame.DecorationActor", DynamicFilterName="Spatial", FilterProfile="Decoration")

        配置结构:

        CPP
        // 📍 源文件:ObjectReplicationBridgeConfig.h
        
        USTRUCT()
        struct FClassFilterConfig
        {
            GENERATED_BODY()
            
            // 类名(完整路径,如 "/Script/Engine.Pawn")
            UPROPERTY(Config)
            FName ClassName;
            
            // 使用的动态过滤器名称("Spatial", "Nop", "Connection", "FilterOut")
            UPROPERTY(Config)
            FName DynamicFilterName;
            
            // 过滤器配置预设名称(对应 HysteresisProfiles 中的 FilterProfileName)
            UPROPERTY(Config)
            FName FilterProfile;
        };

        📋 GridFilter 配置详解

        空间网格过滤器有专门的配置类:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/NetObjectGridFilter.h
        
        UCLASS(transient, config=Engine)
        class UNetObjectGridFilterConfig : public UNetObjectFilterConfig
        {
            GENERATED_BODY()
        
        public:
            // ═══════════════════════════════════════════════════════════
            // 帧计数相关
            // ═══════════════════════════════════════════════════════════
            
            // 视点位置相关性帧数
            // 玩家移动后,之前的单元格仍然相关的帧数
            UPROPERTY(Config)
            uint32 ViewPosRelevancyFrameCount = 2;
            
            // 剔除前等待帧数(防抖动)
            UPROPERTY(Config)
            uint16 DefaultFrameCountBeforeCulling = 4;
            
            // ═══════════════════════════════════════════════════════════
            // 网格尺寸
            // ═══════════════════════════════════════════════════════════
            
            // 网格单元 X 方向尺寸(单位:厘米)
            UPROPERTY(Config)
            float CellSizeX = 20000.0f;  // 200米
            
            // 网格单元 Y 方向尺寸(单位:厘米)
            UPROPERTY(Config)
            float CellSizeY = 20000.0f;  // 200米
            
            // ═══════════════════════════════════════════════════════════
            // 剔除距离
            // ═══════════════════════════════════════════════════════════
            
            // 最大剔除距离(0 = 无限制)
            UPROPERTY(Config)
            float MaxCullDistance = 0.0f;
            
            // 默认剔除距离
            UPROPERTY(Config)
            float DefaultCullDistance = 15000.0f;  // 150米
            
            // ═══════════════════════════════════════════════════════════
            // 距离计算模式
            // ═══════════════════════════════════════════════════════════
            
            // 是否使用精确距离计算
            // true: 使用欧几里得距离(更精确,CPU 开销更大)
            // false: 使用网格归属(更快,但可能同步超出范围的对象)
            UPROPERTY(Config)
            bool bUseExactCullDistance = true;
            
            // ═══════════════════════════════════════════════════════════
            // 配置预设
            // ═══════════════════════════════════════════════════════════
            
            // 不同类型对象的剔除距离预设
            UPROPERTY(Config)
            TArray<FNetObjectGridFilterProfile> FilterProfiles;
        };
        
        // 预设结构USTRUCT()
        struct FNetObjectGridFilterProfile
        {
            UPROPERTY(Config)
            FName ProfileName;                    // 预设名称
            
            UPROPERTY(Config)
            float CullDistance = 15000.0f;        // 剔除距离
            
            UPROPERTY(Config)
            uint16 FrameCountBeforeCulling = 4;   // 剔除前帧数
        };

        GridFilter 配置示例:

        INI
        ; 📍 Config/DefaultEngine.ini
        
        [/Script/IrisCore.NetObjectGridFilterConfig]; 网格尺寸(200米 x 200米)CellSizeX=20000.0CellSizeY=20000.0
        
        ; 默认剔除距离(150米)DefaultCullDistance=15000.0
        
        ; 最大剔除距离(0=无限制,建议设置以防止异常大的值)MaxCullDistance=50000.0
        
        ; 使用精确距离计算bUseExactCullDistance=true
        
        ; 视点位置相关性帧数ViewPosRelevancyFrameCount=2
        
        ; 默认剔除前帧数DefaultFrameCountBeforeCulling=4
        
        ; 清除默认预设
        !FilterProfiles=ClearArray
        
        ; 添加预设
        +FilterProfiles=(ProfileName="Default", CullDistance=15000.0, FrameCountBeforeCulling=4)
        +FilterProfiles=(ProfileName="LongRange", CullDistance=30000.0, FrameCountBeforeCulling=8)
        +FilterProfiles=(ProfileName="ShortRange", CullDistance=5000.0, FrameCountBeforeCulling=2)
        +FilterProfiles=(ProfileName="VeryLongRange", CullDistance=50000.0, FrameCountBeforeCulling=16)

        使用示例:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📋 FilterProfile 使用示例                         │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  类型              FilterProfile    剔除距离    延迟帧数        │
        │  ─────────────────────────────────────────────────────────────│
        │  APlayerCharacter  "LongRange"      300m        8帧            │
        │  AEnemy            "Default"        150m        4帧            │
        │  APickup           "ShortRange"     50m         2帧            │
        │  ADecoration       "ShortRange"     50m         2帧            │
        │  ABoss             "VeryLongRange"  500m        16帧           │
        │                                                                │
        │  这样配置后:                                                   │
        │  - 玩家角色在很远的地方就能看到                                 │
        │  - Boss 在极远距离也能看到(重要目标)                          │
        │  - 敌人在中等距离可见                                          │
        │  - 拾取物和装饰物只在近距离可见                                 │
        │                                                                │
        │  配置文件中的对应配置:                                         │
        │  +FilterConfigs=(ClassName="/Script/MyGame.PlayerCharacter",   │
        │                  DynamicFilterName="Spatial",                  │
        │                  FilterProfile="LongRange")                    │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🎮 运行时配置修改

        某些配置可以通过控制台变量在运行时修改:

        CPP
        // 📍 控制台变量
        
        // 是否剔除不相关的对象(调试用)// Net.Iris.CullNonRelevant 0/1bool bCVarRepFilterCullNonRelevant = true;
        
        // 验证子对象过滤一致性(调试用)// Net.Iris.Filtering.ValidateNobSubObjectInScopeWithFilteredOutRootObject 0/1bool bCVarRepFilterValidateNoSubObjectInScopeWithFilteredOutRootObject = false;
        
        // 组成员数量警告阈值// net.Iris.EnsureIfNumGroupMembershipsExceeds N
        int32 CVarEnsureIfNumGroupMembershipsExceedsNum = 128;

        调试命令示例:

        PLAINTEXT
        ; 在控制台中执行
        
        ; 临时禁用过滤(所有对象都同步)
        Net.Iris.CullNonRelevant 0
        
        ; 重新启用过滤
        Net.Iris.CullNonRelevant 1
        
        ; 启用子对象过滤验证(检测潜在 bug)
        Net.Iris.Filtering.ValidateNobSubObjectInScopeWithFilteredOutRootObject 1

        🔧 5.7 过滤系统内部实现

        🧠 ReplicationFiltering(复制过滤核心)

        FReplicationFiltering 是整个过滤系统的大脑,协调所有过滤器的工作。它是 Iris 过滤系统中最核心的类,理解它的实现对于深入掌握整个系统至关重要。

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.h
        
        class FReplicationFiltering
        {
        public:
            // 🎬 初始化
            void Init(FReplicationFilteringInitParams& Params);
            void Deinit();
            
            // 🔄 主过滤函数 - 每帧调用
            void Filter();
            
            // 📊 获取过滤结果
            const FNetBitArrayView GetRelevantObjectsInScope(uint32 ConnectionId) const;
            const FNetBitArrayView GetGroupFilteredOutObjects(uint32 ConnectionId) const;
            
            // 👤 所有者过滤
            void SetOwningConnection(FInternalNetRefIndex ObjectIndex, uint32 ConnectionId);
            uint32 GetOwningConnection(FInternalNetRefIndex ObjectIndex) const;
            
            // 🎛️ 动态过滤器管理
            bool SetFilter(FInternalNetRefIndex ObjectIndex, FNetObjectFilterHandle Filter, FName FilterConfigProfile);
            FNetObjectFilterHandle GetFilterHandle(const FName FilterName) const;
            
            // 🏢 组过滤
            bool AddExclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
            bool AddInclusionFilterGroup(FNetObjectGroupHandle GroupHandle);
            void SetGroupFilterStatus(FNetObjectGroupHandle GroupHandle, ENetFilterStatus Status);
            
        private:
            // 状态标志位(用于优化,避免不必要的处理)
            uint8 bHasNewConnection : 1;
            uint8 bHasRemovedConnection : 1;
            uint8 bHasDirtyConnectionFilter : 1;
            uint8 bHasDirtyOwner : 1;
            uint8 bHasDynamicFilters : 1;
            uint8 bHasDirtyExclusionFilterGroup : 1;
            uint8 bHasDirtyInclusionFilterGroup : 1;
            uint8 bHasDynamicFiltersWithUpdateTrait : 1;
            
            // 每连接维护的数据
            struct FPerConnectionInfo
            {
                FNetBitArray ConnectionFilteredObjects;      // 连接过滤结果
                FNetBitArray GroupExcludedObjects;           // 组排除的对象
                FNetBitArray GroupIncludedObjects;           // 组包含的对象
                FNetBitArray ObjectsInScopeBeforeDynamicFiltering;  // 动态过滤前
                FNetBitArray ObjectsInScope;                 // 最终范围
                FNetBitArray DynamicFilteredOutObjects;      // 动态过滤掉的
                FObjectScopeHysteresisUpdater HysteresisUpdater;  // 滞后更新器
            };
            
            TArray<FPerConnectionInfo> ConnectionInfos;
            
            // 动态过滤器信息
            struct FFilterInfo
            {
                TObjectPtr<UNetObjectFilter> Filter;
                FName Name;
                uint32 ObjectCount = 0;
            };
            TArray<FFilterInfo> DynamicFilterInfos;
            
            // 对象到过滤器的映射
            TArray<uint8> ObjectIndexToDynamicFilterIndex;
            TArray<uint32> ObjectIndexToOwningConnection;
        };

        初始化流程详解:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.cpp
        
        void FReplicationFiltering::Init(FReplicationFilteringInitParams& Params){
            // 1. 加载配置
            Config = TStrongObjectPtr(GetDefault<UReplicationFilteringConfig>());
            
            // 2. 保存引用
            ReplicationSystem = Params.ReplicationSystem;
            Connections = Params.Connections;
            NetRefHandleManager = Params.NetRefHandleManager;
            Groups = Params.Groups;
            
            // 3. 初始化连接数组
            ConnectionInfos.SetNum(Params.Connections->GetMaxConnectionCount() + 1U);
            ValidConnections.Init(ConnectionInfos.Num());
            NewConnections.Init(ConnectionInfos.Num());
            
            // 4. 初始化对象列表
            SetNetObjectListsSize(MaxInternalNetRefIndex);
            
            // 5. 初始化组过滤
            GroupInfos.SetNumZeroed(MaxGroupCount);
            ExclusionFilterGroups.Init(MaxGroupCount);
            InclusionFilterGroups.Init(MaxGroupCount);
            
            // 6. 初始化动态过滤器
            InitFilters();
            
            // 7. 初始化滞后系统
            InitObjectScopeHysteresis();
        }

        主过滤函数 Filter() 的执行流程:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🧠 FReplicationFiltering::Filter() 执行流程        │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  void FReplicationFiltering::Filter()                         │
        │  {                                                             │
        │      // 阶段 1: 连接管理                                       │
        │      ResetRemovedConnections();   // 清理断开的连接            │
        │      InitNewConnections();        // 初始化新连接              │
        │                                                                │
        │      // 阶段 2: 更新对象范围                                   │
        │      UpdateObjectsInScope();      // 更新所有对象的基础范围    │
        │                                                                │
        │      // 阶段 3: 组过滤(优先级最高)                           │
        │      if (bHasDirtyExclusionFilterGroup)                       │
        │          UpdateGroupExclusionFiltering();  // 排除组           │
        │      if (bHasDirtyInclusionFilterGroup)                       │
        │          UpdateGroupInclusionFiltering();  // 包含组           │
        │                                                                │
        │      // 阶段 4: 所有者和连接过滤                               │
        │      if (bHasDirtyOwner || bHasDirtyConnectionFilter)         │
        │          UpdateOwnerAndConnectionFiltering();                  │
        │                                                                │
        │      // 阶段 5: 子对象过滤                                     │
        │      UpdateSubObjectFilters();                                 │
        │                                                                │
        │      // 阶段 6: 滞后预处理                                     │
        │      PreUpdateObjectScopeHysteresis();                        │
        │                                                                │
        │      // 阶段 7: 动态过滤器(如 GridFilter)                    │
        │      if (bHasDynamicFilters)                                  │
        │          UpdateDynamicFilters();                               │
        │                                                                │
        │      // 阶段 8: 最终过滤                                       │
        │      FilterNonRelevantObjects();                               │
        │  }                                                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        动态过滤器更新的批处理优化:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.cpp
        
        // 批处理助手类 - 优化脏对象的处理class FReplicationFiltering::FUpdateDirtyObjectsBatchHelper
        {
        public:
            enum Constants : uint32
            {
                MaxObjectCountPerBatch = 512U,  // 每批最多处理 512 个对象
            };
            
            // 按过滤器分组对象,减少虚函数调用开销
            void PrepareBatch(const uint32* ObjectIndices, uint32 ObjectCount, 
                              const TArray<uint8>& FilterIndices)
            {
                ResetBatch();
                
                for (const uint32 ObjectIndex : MakeArrayView(ObjectIndices, ObjectCount))
                {
                    const uint8 FilterIndex = FilterIndices[ObjectIndex];
                    if (FilterIndex == InvalidDynamicFilterIndex)
                        continue;
                    
                    FPerFilterInfo& PerFilterInfo = PerFilterInfos[FilterIndex];
                    PerFilterInfo.ObjectIndices[PerFilterInfo.ObjectCount++] = ObjectIndex;
                }
            }
        };

        过滤数据流:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🧠 FReplicationFiltering 数据流                   │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  所有可复制对象                                                 │
        │  [████████████████████████████████████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  1. 组排除过滤                       │                      │
        │  │  移除被排除组包含的对象              │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████████░░░░████████████░░░░████████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  2. 所有者过滤                       │                      │
        │  │  只保留属于当前连接的对象            │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████████░░░░████░░░░████░░░░████████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  3. 连接过滤                         │                      │
        │  │  应用每连接的自定义过滤规则          │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████████░░░░████░░░░░░░░░░░░████████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  4. 动态过滤(空间过滤等)           │                      │
        │  │  基于位置等动态条件过滤              │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████░░░░░░░░████░░░░░░░░░░░░░░░░████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  5. 组包含过滤                       │                      │
        │  │  强制添加包含组中的对象              │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████░░░░░░░░████░░░░████░░░░░░░░████]                       │
        │                    │                                           │
        │                    ▼                                           │
        │  ┌─────────────────────────────────────┐                      │
        │  │  6. 滞后处理                         │                      │
        │  │  延迟移除刚离开范围的对象            │                      │
        │  └─────────────────────────────────────┘                      │
        │  [████░░░░░░░░████░░░░████░░░░░░██████]  ← 最终结果           │
        │                                                                │
        │  ████ = 允许复制    ░░░░ = 禁止复制                           │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        控制台变量(调试用):

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ReplicationFiltering.cpp
        
        // 是否剔除不相关的对象(可用于调试)bool bCVarRepFilterCullNonRelevant = true;
        static FAutoConsoleVariableRef CVarRepFilterCullNonRelevant(
            TEXT("Net.Iris.CullNonRelevant"), 
            bCVarRepFilterCullNonRelevant, 
            TEXT("When enabled will cull replicated actors that are not relevant to any client."),
            ECVF_Default
        );
        
        // 验证子对象过滤的一致性bool bCVarRepFilterValidateNoSubObjectInScopeWithFilteredOutRootObject = false;
        static FAutoConsoleVariableRef CVarRepFilterValidateNoSubObjectInScopeWithFilteredOutRootObject(
            TEXT("Net.Iris.Filtering.ValidateNobSubObjectInScopeWithFilteredOutRootObject"), 
            bCVarRepFilterValidateNoSubObjectInScopeWithFilteredOutRootObject, 
            TEXT("Validate there are no subobjects in scope with a filtered out root object."),
            ECVF_Default
        );

        ⏱️ ObjectScopeHysteresisUpdater(对象范围滞后更新器)

        "滞后"(Hysteresis)是一个防抖动机制,防止对象在范围边界频繁进出。这个类是滞后机制的核心实现。

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ObjectScopeHysteresisUpdater.h
        
        class FObjectScopeHysteresisUpdater
        {
        public:
            void Init(uint32 MaxObjectCount);
            void Deinit();
            
            // 索引增长时调用
            void OnMaxInternalNetRefIndexIncreased(FInternalNetRefIndex NewMaxInternalIndex);
            
            // 设置对象的滞后帧数(对象进入范围时调用)
            void SetHysteresisFrameCount(FInternalNetRefIndex NetRefIndex, uint16 HysteresisFrameCount);
            
            // 移除对象的滞后状态(对象被销毁时调用)
            void RemoveHysteresis(FInternalNetRefIndex NetRefIndex);
            void RemoveHysteresis(const FNetBitArrayView& ObjectsToRemove);
            void RemoveHysteresis(TArrayView<const uint32> ObjectsToRemove);
            
            // 更新滞后状态,返回应该被过滤掉的对象
            void Update(uint8 FramesSinceLastUpdate, TArray<FInternalNetRefIndex>& OutObjectsToFilterOut);
            
            // 是否有需要更新的对象
            bool HasObjectsToUpdate() const;
            
            // 获取正在更新的对象位图
            FNetBitArrayView GetUpdatedObjects() const;
            
        private:
            enum : unsigned
            {
                LocalIndexGrowCount = 256U,  // 每次增长 256 个槽位
            };
            
            typedef uint32 FLocalIndex;
            
            // 本地索引管理(类似 ConnectionFilter 的优化策略)
            FLocalIndex GetOrCreateLocalIndex(FInternalNetRefIndex NetRefIndex);
            void FreeLocalIndex(FLocalIndex LocalIndex);
            
            // 每个本地索引的帧计数器
            TArray<uint16> FrameCounters;
            
            // 本地索引 → 网络对象索引
            TArray<FInternalNetRefIndex> LocalIndexToNetRefIndex;
            
            // 网络对象索引 → 本地索引(用于快速查找)
            TMap<FInternalNetRefIndex, FLocalIndex> NetRefIndexToLocalIndex;
            
            // 已使用的本地索引位图
            FNetBitArray UsedLocalIndices;
            
            // 正在更新的对象位图(按网络对象索引)
            FNetBitArray ObjectsToUpdate;
        };

        Update 方法的 SIMD 优化实现:

        CPP
        // 📍 源文件:Core/Private/Iris/ReplicationSystem/Filtering/ObjectScopeHysteresisUpdater.cpp
        
        void FObjectScopeHysteresisUpdater::Update(
            uint8 FramesSinceLastUpdate, 
            TArray<FInternalNetRefIndex>& OutObjectsToFilterOut){
            IRIS_PROFILER_SCOPE(FObjectScopeHysteresisUpdater_Update);
            
            ensure(FramesSinceLastUpdate > 0 && FramesSinceLastUpdate <= 128);
            
            // 用于检测计数器下溢的比较值
            // 当 Counter - FramesSinceLastUpdate 产生下溢时,结果 >= FilterOutCompareValue
            const uint16 FilterOutCompareValue = (65536U - FramesSinceLastUpdate) & 65535U;
            uint16* CountersData = FrameCounters.GetData();
            
            TArray<FInternalNetRefIndex, TInlineAllocator<32>> ObjectsToRemoveFromUpdate;
            
            // 批量处理:每次处理 4 个计数器(SIMD 友好)
            const WordType* LocalIndicesData = UsedLocalIndices.GetData();
            for (FLocalIndex ObjectIt = 0; ObjectIt < UsedLocalIndices.GetNumBits(); 
                 ObjectIt += WordBitCount, ++LocalIndicesData)
            {
                WordType LocalIndicesWord = *LocalIndicesData;
                if (!LocalIndicesWord)
                    continue;  // 跳过空的字
                
                // 每次处理 4 个索引
                for (WordType LocalIndexOffset = 0; LocalIndexOffset < WordBitCount; 
                     LocalIndexOffset += 4U)
                {
                    uint16 Counters[4];
                    // 批量读取 4 个计数器
                    Counters[0] = CountersData[IndexOffset + LocalIndexOffset + 0];
                    Counters[1] = CountersData[IndexOffset + LocalIndexOffset + 1];
                    Counters[2] = CountersData[IndexOffset + LocalIndexOffset + 2];
                    Counters[3] = CountersData[IndexOffset + LocalIndexOffset + 3];
                    
                    // 批量递减
                    Counters[0] -= FramesSinceLastUpdate;
                    Counters[1] -= FramesSinceLastUpdate;
                    Counters[2] -= FramesSinceLastUpdate;
                    Counters[3] -= FramesSinceLastUpdate;
                    
                    // 批量写回
                    CountersData[IndexOffset + LocalIndexOffset + 0] = Counters[0];
                    CountersData[IndexOffset + LocalIndexOffset + 1] = Counters[1];
                    CountersData[IndexOffset + LocalIndexOffset + 2] = Counters[2];
                    CountersData[IndexOffset + LocalIndexOffset + 3] = Counters[3];
                    
                    // 检查哪些对象应该被过滤
                    for (uint32 Offset : {0, 1, 2, 3})
                    {
                        if (Counters[Offset] >= FilterOutCompareValue)
                        {
                            // 计数器下溢 → 对象应该被过滤
                            ObjectsToRemoveFromUpdate.Add(IndexOffset + LocalIndexOffset + Offset);
                        }
                    }
                }
                
                // 批量移除
                for (FLocalIndex LocalIndex : ObjectsToRemoveFromUpdate)
                {
                    OutObjectsToFilterOut.Add(LocalIndexToNetRefIndex[LocalIndex]);
                    FreeLocalIndex(LocalIndex);
                }
                ObjectsToRemoveFromUpdate.Reset();
            }
        }

        滞后机制工作原理:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              ⏱️ 滞后机制工作原理                               │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  场景:对象在范围边界来回移动                                   │
        │                                                                │
        │  没有滞后时:                                                   │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │ 帧1: 在范围内 ✅  →  帧2: 离开范围 ❌  →  帧3: 回来 ✅   │  │
        │  │ 帧4: 又离开 ❌   →  帧5: 又回来 ✅    →  帧6: 又离开 ❌  │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │  结果:对象疯狂闪烁!玩家体验极差 😵                           │
        │                                                                │
        │  有滞后时(滞后帧数 = 4):                                     │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │ 帧1: 在范围内 ✅ (计数器=4)                              │  │
        │  │ 帧2: 离开范围,但计数器=3,仍然 ✅                        │  │
        │  │ 帧3: 回来了!计数器重置=4 ✅                              │  │
        │  │ 帧4: 在范围内 ✅                                         │  │
        │  │ ...                                                      │  │
        │  │ 只有连续4帧都在范围外,才会真正被过滤掉                   │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │  结果:对象平滑过渡,没有闪烁 😊                               │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        计数器下溢检测技巧:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔢 计数器下溢检测                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  问题:如何高效检测 Counter - N < 0?                          │
        │                                                                │
        │  传统方法:                                                     │
        │  if (Counter >= FramesSinceLastUpdate)                         │
        │      Counter -= FramesSinceLastUpdate;                         │
        │  else                                                          │
        │      // 应该过滤                                                │
        │                                                                │
        │  优化方法(利用无符号整数下溢):                               │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  FilterOutCompareValue = 65536 - FramesSinceLastUpdate  │  │
        │  │                                                          │  │
        │  │  Counter -= FramesSinceLastUpdate;  // 可能下溢          │  │
        │  │                                                          │  │
        │  │  if (Counter >= FilterOutCompareValue)                   │  │
        │  │      // 下溢发生 → 应该过滤                              │  │
        │  │                                                          │  │
        │  │  示例(FramesSinceLastUpdate = 1):                     │  │
        │  │  FilterOutCompareValue = 65535                           │  │
        │  │  Counter = 0 → 0 - 1 = 65535 (下溢) ≥ 65535 ✓ 过滤      │  │
        │  │  Counter = 1 → 1 - 1 = 0 < 65535 ✗ 不过滤               │  │
        │  │  Counter = 4 → 4 - 1 = 3 < 65535 ✗ 不过滤               │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  优点:避免分支,SIMD 友好                                     │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        滞后时序图:

        PLAINTEXT
        时间 →      帧1      帧2      帧3      帧4      帧5      帧6      帧7      帧8
                   ─────────────────────────────────────────────────────────────────────
        对象位置    范围内    范围外   范围外   范围外   范围外   范围外   范围内   范围内
        
        计数器        4        3        2        1        0        -        4        4
        
        实际状态     ✅       ✅       ✅      ✅       ❌      ❌       ✅       ✅
                                                          ↑
                                                    这里才真正被过滤
        
        Update() 调用流程:
        帧2: SetHysteresisFrameCount(obj, 4) → Counter=4
        帧3: Update(1) → Counter=3, 仍在范围
        帧4: Update(1) → Counter=2, 仍在范围
        帧5: Update(1) → Counter=1, 仍在范围
        帧6: Update(1) → Counter=0, 下溢检测触发 → OutObjectsToFilterOut.Add(obj)
        帧7: 对象回到范围 → SetHysteresisFrameCount(obj, 4) → Counter=4

        本地索引优化:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              📊 本地索引优化                                    │├────────────────────────────────────────────────────────────────┤│                                                                ││  问题:网络对象索引可能很稀疏(0~65535),但实际需要滞后的      ││        对象可能只有几百个                                       ││                                                                ││  解决方案:使用本地索引映射                                     ││  ┌─────────────────────────────────────────────────────────┐  ││  │  NetRefIndexToLocalIndex: Map<uint32, uint32>           │  ││  │  LocalIndexToNetRefIndex: Array<uint32>                 │  ││  │  FrameCounters: Array<uint16>                           │  ││  │                                                          │  ││  │  网络对象索引        本地索引       帧计数器             │  ││  │  1234          →      0       →      4                  │  ││  │  5678          →      1       →      8                  │  ││  │  9012          →      2       →      2                  │  ││  │  ...                                                     │  ││  └─────────────────────────────────────────────────────────┘  ││                                                                ││  优点:                                                        ││  • FrameCounters 数组紧凑,缓存友好                           ││  • 批量处理时内存访问连续                                      ││  • 动态增长(每次 256 个槽位)                                 ││                                                                │└────────────────────────────────────────────────────────────────┘

        🔗 SharedConnectionFilterStatus(共享连接过滤状态)

        用于处理分屏游戏等场景,多个玩家共享同一个网络连接。

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Filtering/SharedConnectionFilterStatus.h
        
        class FSharedConnectionFilterStatus
        {
        public:
            // 设置某个子连接的过滤状态
            bool SetFilterStatus(FConnectionHandle ConnectionHandle, ENetFilterStatus FilterStatus);
            
            // 获取组的过滤状态(任一子连接允许则允许)
            ENetFilterStatus GetFilterStatus() const;
            
            // 移除子连接
            void RemoveConnection(FConnectionHandle ConnectionHandle);
            
        private:
            TSet<uint32> AllowConnections;  // 允许复制的子连接集合
            uint32 ParentConnectionId;       // 父连接ID
        };

        分屏游戏场景:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔗 分屏游戏共享连接                               │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  一台主机,两个玩家分屏游戏                                     │
        │                                                                │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │                    主机                                  │  │
        │  │  ┌─────────────────┬─────────────────┐                  │  │
        │  │  │    玩家1视图     │    玩家2视图     │                  │  │
        │  │  │      👤         │       👤        │                  │  │
        │  │  │   (子连接1)     │    (子连接2)     │                  │  │
        │  │  └─────────────────┴─────────────────┘                  │  │
        │  │                    │                                     │  │
        │  │            ┌───────┴───────┐                            │  │
        │  │            │  父连接        │                            │  │
        │  │            │  (网络连接)    │                            │  │
        │  │            └───────┬───────┘                            │  │
        │  └────────────────────┼────────────────────────────────────┘  │
        │                       │                                        │
        │                       ▼                                        │
        │                    服务器                                       │
        │                                                                │
        │  对于某个对象:                                                 │
        │  - 子连接1(玩家1): Allow                                     │
        │  - 子连接2(玩家2): Disallow                                  │
        │  - 父连接的最终状态: Allow(因为有一个子连接允许)              │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📊 5.8 过滤系统总结

        🎯 核心概念回顾

        概念

        说明

        过滤器 (Filter)

        决定对象是否应该被同步的组件

        组 (Group)

        批量管理对象可见性的容器

        滞后 (Hysteresis)

        防止对象在边界闪烁的机制

        范围 (Scope)

        对某个连接可见的对象集合

        📋 过滤优先级

        PLAINTEXT
        优先级从高到低:
        1️⃣ 组排除过滤 (Exclusion Group)     - 最高优先级,直接排除
        2️⃣ 所有者过滤 (Owner)               - 只同步给拥有者
        3️⃣ 连接过滤 (Connection)            - 针对特定连接
        4️⃣ 动态过滤 (Dynamic/Spatial)       - 基于位置等条件
        5️⃣ 组包含过滤 (Inclusion Group)     - 强制包含,可覆盖动态过滤
        6️⃣ 滞后处理 (Hysteresis)            - 延迟移除

        💡 最佳实践

        1. 选择合适的过滤器

          • 大世界游戏 → GridFilter

          • 私有数据 → ConnectionFilter

          • 关卡流送 → Group Filtering

        2. 合理配置剔除距离

          • 重要对象(玩家、Boss)→ 大剔除距离

          • 次要对象(装饰物)→ 小剔除距离

        3. 使用滞后防止闪烁

          • 快速移动的对象 → 较大滞后帧数

          • 静态对象 → 较小滞后帧数

        4. 利用组过滤管理关卡

          • 每个流送关卡一个组

          • 加载时 Allow,卸载时 Disallow

        📁 关键源文件

        PLAINTEXT
        Engine/Source/Runtime/Experimental/Iris/Core/
        ├── Public/Iris/ReplicationSystem/Filtering/
        │   ├── NetObjectFilter.h              # 过滤器基类
        │   ├── NopNetObjectFilter.h           # 空操作过滤器
        │   ├── FilterOutNetObjectFilter.h     # 全部过滤
        │   ├── NetObjectGridFilter.h          # 空间网格过滤器
        │   ├── NetObjectConnectionFilter.h    # 连接过滤器
        │   ├── NetObjectFilterDefinitions.h   # 过滤器定义
        │   ├── ReplicationFilteringConfig.h   # 过滤配置
        │   └── SharedConnectionFilterStatus.h # 共享连接状态
        │
        └── Private/Iris/ReplicationSystem/Filtering/
            ├── ReplicationFiltering.h/.cpp    # 核心过滤系统
            ├── NetObjectFilter.cpp            # 基类实现
            ├── NetObjectGridFilter.cpp        # 网格过滤器实现
            ├── NetObjectGroups.h/.cpp         # 对象组管理
            ├── ObjectScopeHysteresisUpdater.h/.cpp  # 滞后更新器
            └── SharedConnectionFilterStatus.cpp     # 共享状态实现

        🎉 恭喜! 你已经完成了 Iris 过滤系统的学习。过滤系统是网络复制优化的关键,掌握它能让你的多人游戏更加流畅高效!

        📖 下一步:继续学习第六部分「优先级系统」,了解如何在有限带宽下优先同步重要对象。


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

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