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

        🎯 Iris 网络复制系统技术分析 - 第六部分:优先级系统 (Prioritization)

        想象一下,你是一个忙碌的外卖小哥🛵,手上有50份外卖要送,但每趟只能带10份。你会怎么决定先送哪些?

        • 🔥 快超时的订单?(紧急度)

        • 📍 离你最近的?(距离)

        • 💰 打赏多的土豪?(重要性)

        • 👀 正在盯着APP看配送进度的?(关注度)

        Iris 的优先级系统就是帮服务器做这个决定的"智能调度员"📋,它决定:在带宽有限的情况下,哪些对象的数据应该优先发送给玩家。


        📚 6.1 优先级系统概述

        🎯 优先级的作用

        在大型多人游戏中,服务器可能需要同步成千上万个对象给每个玩家。但网络带宽是有限的——就像外卖小哥的电动车后座只能放这么多餐盒🍱。

        没有优先级系统会怎样?

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              😱 没有优先级的混乱世界                             │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  服务器:"我有1000个对象要同步,带宽只够发100个..."             │
        │                                                                │
        │  发送队列(随机顺序):                                         │
        │  [远处的树🌳] [脚下的地雷💣] [天边的云☁️] [敌人的子弹🔫]        │
        │  [背后的宝箱🎁] [正在攻击你的Boss👹] [装饰花盆🪴]...            │
        │                                                                │
        │  结果:                                                        │
        │  • 玩家被"隐形"子弹打死 → "这游戏有挂!"                      │
        │  • Boss 攻击动作卡顿 → "网络太差了!"                         │
        │  • 重要道具延迟出现 → "BUG!道具不刷新!"                     │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        有了优先级系统之后:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              ✅ 智能优先级调度                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  优先级排序后:                                                 │
        │  [正在攻击你的Boss👹] 优先级: 1.0  ← 最近+正在交互             │
        │  [敌人的子弹🔫]       优先级: 0.95 ← 飞向你的!                │
        │  [脚下的地雷💣]       优先级: 0.9  ← 很近很危险                │
        │  [背后的宝箱🎁]       优先级: 0.7  ← 近但不紧急                │
        │  [远处的树🌳]         优先级: 0.1  ← 装饰,不重要              │
        │  [天边的云☁️]         优先级: 0.05 ← 超远,随缘                │
        │                                                                │
        │  结果:重要的先发,不重要的等带宽空闲再发                       │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📊 静态优先级 vs 动态优先级

        类型

        比喻

        特点

        适用场景

        静态优先级

        🍽️ 套餐固定价

        一次设定,永不改变

        背景装饰、不重要的NPC

        动态优先级

        📊 实时竞价

        每帧根据情况重新计算

        玩家、敌人、重要道具

        CPP
        // 📍 源文件:ReplicationPrioritization.h
        
        class FReplicationPrioritization
        {
            // 静态优先级:设置一次,永久生效
            void SetStaticPriority(uint32 ObjectIndex, float Priority);
            
            // 动态优先级:通过 Prioritizer 每帧计算
            bool SetPrioritizer(uint32 ObjectIndex, FNetObjectPrioritizerHandle Prioritizer);
            
            // 默认优先级常量
            static constexpr float DefaultPriority = 1.0f;
            
            // 视图目标(玩家控制的角色)获得超高优先级
            static constexpr float ViewTargetHighPriority = 1.0E7f;  // 一千万!
        };

        ⏰ 优先级计算时机

        优先级计算发生在 PreSendUpdate 阶段,在过滤之后执行:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔄 PreSendUpdate 执行流程                          │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  1️⃣ UpdateDirtyObjectList()                                    │
        │      └─→ 收集本帧有变化的对象                                   │
        │                                                                │
        │  2️⃣ UpdateWorldLocations()                                     │
        │      └─→ 更新对象位置(过滤和优先级都需要)                     │
        │                                                                │
        │  3️⃣ UpdateFiltering()          ⬅️ 先过滤!                     │
        │      └─→ 确定每个连接的对象作用域                               │
        │                                                                │
        │  4️⃣ UpdateConditionals()                                       │
        │      └─→ 更新条件复制状态                                       │
        │                                                                │
        │  5️⃣ UpdatePrioritization()     ⬅️ 后计算优先级!               │
        │      └─→ 为过滤后的对象计算优先级                               │
        │                                                                │
        │  📌 为什么这个顺序?                                            │
        │  • 过滤后对象数量大大减少                                       │
        │  • 优先级只需要计算"可能被复制"的对象                          │
        │  • 节省大量计算开销                                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        CPP
        // 📍 源文件:ReplicationSystem.cppvoid UReplicationSystem::PreSendUpdate(const FSendUpdateParams& Params){
            // 1. 更新脏对象列表
            Impl->UpdateDirtyObjectList();
        
            // 2. 更新世界位置(过滤和优先级都需要)
            Impl->UpdateWorldLocations();
        
            // 3. 先执行过滤,确定每个连接的作用域
            Impl->UpdateFiltering();
        
            // 4. 调用 PreSendUpdate 回调
            Impl->CallPreSendUpdate(Params.DeltaSeconds);
        
            // 5. 更新条件复制
            Impl->UpdateConditionals();
        
            // 6. 最后执行优先级计算(在过滤之后)
            Impl->UpdatePrioritization(ReplicatingConnections);
        }

        🔢 优先级值的含义

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📊 优先级数值含义                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  优先级值         含义                                          │
        │  ─────────────────────────────────────────────────────────────│
        │  1.0E7f (1000万)  视图目标(玩家角色)- 绝对最高               │
        │  1.0f             标准优先级 - 考虑复制的阈值                   │
        │  0.5f             中等优先级 - 可能被复制                       │
        │  0.1f             低优先级 - 带宽充足时才复制                   │
        │  0.0f             不复制 - 完全跳过                             │
        │                                                                │
        │  📌 重要机制:优先级会累积!                                    │
        │  帧1: 对象A优先级 = 0.3 (未被复制,累积)                       │
        │  帧2: 对象A优先级 = 0.6 (仍未复制)                             │
        │  帧3: 对象A优先级 = 0.9 (仍未复制)                             │
        │  帧4: 对象A优先级 = 1.2 (超过1.0,被复制!然后重置)            │
        │                                                                │
        │  这个机制防止低优先级对象永远"饿死"!                          │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🧱 6.2 UNetObjectPrioritizer 基类

        📋 Prioritizer 接口定义

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/NetObjectPrioritizer.h
        
        /**
         * 优先级专用的每对象数据结构
         * 大小固定为 8 字节,用于存储优先级特定的信息
         * 子类通过继承此结构来存储自己需要的数据
         */struct alignas(8) FNetObjectPrioritizationInfo
        {
            uint16 Data[4];  // 4 个 uint16 = 4 × 2 字节 = 8 字节 可被子类重新解释使用
        };
        
        /**
         * 优先级配置基类
         * 子类通过 UPROPERTY(Config) 暴露配置参数到 ini 文件
         */UCLASS(Transient, MinimalAPI)
        class UNetObjectPrioritizerConfig : public UObject
        {
            GENERATED_BODY()
        };
        
        /**
         * 优先级抽象基类
         * 定义了优先级的完整生命周期和核心接口
         */UCLASS(Abstract)
        class UNetObjectPrioritizer : public UObject
        {
            GENERATED_BODY()
        
        public:
            // ═══════════════════════════════════════════════════════════════
            // 🎬 生命周期管理
            // ═══════════════════════════════════════════════════════════════
            
            /**
             * 初始化优先级
             * @param Params 包含 ReplicationSystem、Config、最大对象数等信息
             * 在 FReplicationPrioritization::InitPrioritizers() 中调用
             */
            IRISCORE_API virtual void Init(FNetObjectPrioritizerInitParams& Params) PURE_VIRTUAL(Init,);
            
            /**
             * 清理优先级
             * 在复制系统关闭时调用,释放所有资源
             */
            IRISCORE_API virtual void Deinit() PURE_VIRTUAL(Deinit,);
            
            /**
             * 当最大内部索引增加时调用
             * 用于重新分配内部数组以容纳更多对象
             * @param NewMaxInternalIndex 新的最大内部索引
             */
            IRISCORE_API virtual void OnMaxInternalNetRefIndexIncreased(uint32 NewMaxInternalIndex) 
                PURE_VIRTUAL(OnMaxInternalNetRefIndexIncreased,);
            
            // ═══════════════════════════════════════════════════════════════
            // 🔌 连接管理
            // ═══════════════════════════════════════════════════════════════
            
            /**
             * 新连接添加时调用
             * 用于初始化每连接的数据(如 Fill 模式的帧计数数组)
             * 基类实现为空,子类按需重写
             */
            IRISCORE_API virtual void AddConnection(uint32 ConnectionId);
            
            /**
             * 连接移除时调用
             * 用于清理每连接的数据
             */
            IRISCORE_API virtual void RemoveConnection(uint32 ConnectionId);
            
            // ═══════════════════════════════════════════════════════════════
            // 📦 对象管理
            // ═══════════════════════════════════════════════════════════════
            
            /**
             * 新对象要使用此优先级时调用
             * @param ObjectIndex 对象的内部索引
             * @param Params 包含 Protocol、InstanceProtocol、StateBuffer 等
             * @return true 表示成功添加,false 表示此优先级不支持该对象
             */
            IRISCORE_API virtual bool AddObject(uint32 ObjectIndex, FNetObjectPrioritizerAddObjectParams& Params) 
                PURE_VIRTUAL(AddObject, return false;);
            
            /**
             * 对象不再使用此优先级时调用
             * 用于释放对象相关的资源(如位置索引)
             */
            IRISCORE_API virtual void RemoveObject(uint32 ObjectIndex, const FNetObjectPrioritizationInfo& Info) 
                PURE_VIRTUAL(RemoveObject,);
            
            /**
             * 一组对象已更新(脏)时调用
             * 用于更新对象的位置等信息
             * 在 NotifyPrioritizersOfDirtyObjects() 中批量调用
             */
            IRISCORE_API virtual void UpdateObjects(FNetObjectPrioritizerUpdateParams&) PURE_VIRTUAL(UpdateObjects,);
            
            // ═══════════════════════════════════════════════════════════════
            // 🎯 优先级计算(核心!)
            // ═══════════════════════════════════════════════════════════════
            
            /**
             * 在所有 Prioritize() 调用之前调用一次
             * 用于准备工作,如 RoundRobin 模式选择本帧要考虑的对象
             * 基类实现为空
             */
            IRISCORE_API virtual void PrePrioritize(FNetObjectPrePrioritizationParams&);
            
            /**
             * ⭐ 核心方法:批量计算优先级
             * 可能对同一连接多次调用(如果对象数量超过批大小)
             * @param Params 包含对象列表、优先级输出数组、视图信息等
             */
            IRISCORE_API virtual void Prioritize(FNetObjectPrioritizationParams&) PURE_VIRTUAL(Prioritize,);
            
            /**
             * 在所有 Prioritize() 调用之后调用一次
             * 用于清理工作
             * 基类实现为空
             */
            IRISCORE_API virtual void PostPrioritize(FNetObjectPostPrioritizationParams&);
        };
        
        // 特殊句柄常量constexpr FNetObjectPrioritizerHandle InvalidNetObjectPrioritizerHandle = ~FNetObjectPrioritizerHandle(0);
        constexpr FNetObjectPrioritizerHandle DefaultSpatialNetObjectPrioritizerHandle = FNetObjectPrioritizerHandle(0);

        🔑 关键设计解析

        为什么 FNetObjectPrioritizationInfo 只有 8 字节?

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📦 FNetObjectPrioritizationInfo 设计原理           │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  设计约束:                                                     │
        │  • 每个可复制对象都需要一个 Info 结构                          │
        │  • 大型游戏可能有 10000+ 对象                                  │
        │  • 内存占用 = 对象数 × Info大小                                │
        │                                                                │
        │  8字节设计:                                                    │
        │  uint16 Data[4] = { 状态偏移, 状态索引, 位置索引低16位, 位置索引高16位 }│
        │                                                                │
        │  内存计算:                                                     │
        │  10000对象 × 8字节 = 80KB(可接受)                            │
        │  10000对象 × 64字节 = 640KB(太大!)                          │
        │                                                                │
        │  子类通过 static_cast 重新解释这 8 字节:                       │
        │  • FObjectLocationInfo: 存储位置相关信息                       │
        │  • FObjectInfo: 存储内部索引和所有者连接                       │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🎯 Prioritize 参数详解

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/NetObjectPrioritizer.h
        
        /**
         * 优先级计算的核心参数结构
         * 包含了计算优先级所需的所有输入和输出
         */struct FNetObjectPrioritizationParams
        {
            // ═══════════════════════════════════════════════════════════════
            // 📥 输入参数
            // ═══════════════════════════════════════════════════════════════
            
            const uint32* ObjectIndices;     // 需要计算优先级的对象索引数组
            uint32 ObjectCount;              // 本批次的对象数量(最多 1024)
            
            const FNetObjectPrioritizationInfo* PrioritizationInfos;  // 对象信息数组(全局)
            uint32 ConnectionId;             // 当前连接ID
            UE::Net::FReplicationView View;  // ⭐视图信息(位置、方向、FOV)
            
            // ═══════════════════════════════════════════════════════════════
            // 📤 输出参数
            // ═══════════════════════════════════════════════════════════════
            
            float* Priorities;               // ⭐输出:优先级数组(全局索引)
                                             // Priorities[ObjectIndices[i]] = 计算结果
        };
        
        /**
         * AddObject 的参数结构
         */struct FNetObjectPrioritizerAddObjectParams
        {
            FNetObjectPrioritizationInfo& OutInfo;           // 输出:填充对象信息
            const FReplicationInstanceProtocol* InstanceProtocol;  // 实例协议
            const FReplicationProtocol* Protocol;            // 复制协议
            const uint8* StateBuffer;                        // 状态缓冲区
        };
        
        /**
         * UpdateObjects 的参数结构
         */struct FNetObjectPrioritizerUpdateParams
        {
            const uint32* ObjectIndices;                     // 脏对象索引数组
            uint32 ObjectCount;                              // 脏对象数量
            const FReplicationInstanceProtocol* const* InstanceProtocols;  // 实例协议数组
            FNetObjectPrioritizationInfo* PrioritizationInfos;  // 对象信息数组(可写)
            const TArray<uint8*>* StateBuffers;              // 状态缓冲区数组
        };

        调用模式详解:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔄 每帧优先级计算完整流程                          │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  FReplicationPrioritization::Prioritize()                      │
        │       │                                                        │
        │       ├─→ UpdatePrioritiesForNewAndDeletedObjects()            │
        │       │       └─→ 处理新增/删除对象的优先级                    │
        │       │                                                        │
        │       ├─→ NotifyPrioritizersOfDirtyObjects(DirtyObjects)       │
        │       │       │                                                │
        │       │       └─→ for each Prioritizer:                        │
        │       │               Prioritizer->UpdateObjects(脏对象列表)   │
        │       │               └─→ 更新位置等信息                       │
        │       │                                                        │
        │       ├─→ for each Prioritizer with objects:                   │
        │       │       Prioritizer->PrePrioritize()  ← 调用一次         │
        │       │       └─→ RoundRobin: 选择本帧要考虑的对象             │
        │       │       └─→ Fill: 无操作(每连接独立处理)               │
        │       │                                                        │
        │       ├─→ for each Connection with View:                       │
        │       │       │                                                │
        │       │       ├─→ PrioritizeForConnection(ConnId, Objects)     │
        │       │       │       │                                        │
        │       │       │       ├─→ 按优先级分组对象                   │
        │       │       │       │                                        │
        │       │       │       └─→ for each Prioritizer batch:          │
        │       │       │               Prioritizer->Prioritize(Params)  │
        │       │       │               └─→ 计算优先级,写入 Priorities  │
        │       │       │                                                │
        │       │       └─→ SetHighPriorityOnViewTargets()               │
        │       │               └─→ Controller/ViewTarget = 1.0E7f       │
        │       │                                                        │
        │       │       └─→ ReplicationWriter->UpdatePriorities()        │
        │       │               └─→ 累加优先级到 SchedulingPriorities    │
        │       │                                                        │
        │       └─→ for each Prioritizer with objects:                   │
        │               Prioritizer->PostPrioritize()  ← 调用一次        │
        │                                                                │
        │  📌 关键点:                                                    │
        │  • PrePrioritize/PostPrioritize 每帧各调用一次                 │
        │  • Prioritize 每连接可能调用多次(批处理)                     │
        │  • 同一对象对不同连接有不同优先级!                            │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🤝 与过滤系统的协作

        优先级系统和过滤系统紧密配合,形成"先筛选后排序"的高效流水线:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔗 过滤系统 → 优先级系统 协作流程                   │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  原始对象池(10000个对象)                                      │
        │       │                                                        │
        │       ▼                                                        │
        │  ┌──────────────────────────────────────┐                      │
        │  │  🔍 过滤系统 (UpdateFiltering)       │                      │
        │  │  • 空间过滤:剔除视野外对象          │                      │
        │  │  • 组过滤:剔除不相关关卡            │                      │
        │  │  • 连接过滤:剔除特定连接对象        │                      │
        │  └──────────────────────────────────────┘                      │
        │       │                                                        │
        │       ▼ 过滤后(500个对象)                                    │
        │  ┌──────────────────────────────────────┐                      │
        │  │  📊 优先级系统 (UpdatePrioritization)│                      │
        │  │  • 距离优先级                        │                      │
        │  │  • 视野优先级                        │                      │
        │  │  • 所有者加成                        │                      │
        │  └──────────────────────────────────────┘                      │
        │       │                                                        │
        │       ▼ 排序后(按优先级发送)                                 │
        │  ┌──────────────────────────────────────┐                      │
        │  │  📤 ReplicationWriter                │                      │
        │  │  • 优先级累积                        │                      │
        │  │  • 选择本帧发送的对象                │                      │
        │  └──────────────────────────────────────┘                      │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        关键代码:过滤后对象传递给优先级系统

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::Prioritize(
            const FNetBitArrayView& ReplicatingConnections, 
            const FNetBitArrayView& DirtyObjectsThisFrame){
            // ...
            for (const uint32 ConnId : MakeArrayView(ConnectionIds, ReplicatingConnectionCount))
            {
                // 检查连接是否有视图
                if (Connections->GetReplicationView(ConnId).Views.Num() <= 0)
                    continue;
        
                FReplicationConnection* Connection = Connections->GetConnection(ConnId);
                FReplicationWriter* ReplicationWriter = Connection->ReplicationWriter;
                
                // 🔑 关键:从 ReplicationWriter 获取需要优先级更新的对象
                // 这些对象已经通过过滤系统的筛选!
                const FNetBitArray& Objects = ReplicationWriter->GetObjectsRequiringPriorityUpdate();
                
                if (Objects.GetNumBits() == 0) continue;
        
                PrioritizeForConnection(ConnId, BatchHelper, MakeNetBitArrayView(Objects));
                
                // 将更新后的优先级传回 ReplicationWriter
                ReplicationWriter->UpdatePriorities(ConnInfo.Priorities.GetData());
            }
        }

        🎛️ 6.3 内置优先级

        Iris 提供了一套完整的优先级继承体系,满足不同游戏类型的需求:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🏗️ 优先级继承层次                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  UNetObjectPrioritizer (抽象基类)                              │
        │       │                                                        │
        │       ├── ULocationBasedNetObjectPrioritizer (抽象,位置管理)  │
        │       │       │                                                │
        │       │       ├── USphereNetObjectPrioritizer (球形距离)       │
        │       │       │       │                                        │
        │       │       │       └── USphereWithOwnerBoostNetObjectPrioritizer│
        │       │       │                                                │
        │       │       └── UFieldOfViewNetObjectPrioritizer (视野锥)    │
        │       │                                                        │
        │       └── UNetObjectCountLimiter (数量限制,RoundRobin/Fill)   │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        📍 LocationBasedNetObjectPrioritizer(位置基础类)

        这是一个抽象基类,为所有基于位置的优先级提供位置管理基础设施。

        "我负责管理位置数据,子类负责计算优先级!" 📍

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/LocationBasedNetObjectPrioritizer.h
        
        UCLASS(Transient, MinimalAPI, Abstract)
        class ULocationBasedNetObjectPrioritizer : public UNetObjectPrioritizer
        {
            GENERATED_BODY()
        
        protected:
            // ═══════════════════════════════════════════════════════════════
            // 📦 位置信息结构(重新解释 FNetObjectPrioritizationInfo 的 8 字节)
            // ═══════════════════════════════════════════════════════════════
            struct FObjectLocationInfo : public FNetObjectPrioritizationInfo
            {
                // Data[0]: 位置状态偏移(在状态缓冲区中的偏移)
                void SetLocationStateOffset(uint16 Offset) { Data[0] = Offset; }
                uint16 GetLocationStateOffset() const { return Data[0]; }
                
                // Data[1]: 位置状态索引(Fragment 索引)
                // InvalidStateIndex (0xFFFF) 表示使用 WorldLocations
                void SetLocationStateIndex(uint16 Index) { Data[1] = Index; }
                uint16 GetLocationStateIndex() const { return Data[1]; }
                
                // Data[2-3]: 位置索引(在 Locations 数组中的索引,32位)
                void SetLocationIndex(uint32 Index) { 
                    Data[2] = Index & 65535U; 
                    Data[3] = Index >> 16U; 
                }
                uint32 GetLocationIndex() const { 
                    return (uint32(Data[3]) << 16U) | uint32(Data[2]); 
                }
                
                // 判断位置来源
                bool IsUsingWorldLocations() const { 
                    return GetLocationStateIndex() == InvalidStateIndex; 
                }
                bool IsUsingLocationInState() const { 
                    return GetLocationStateIndex() != InvalidStateIndex; 
                }
            };
        
            // ═══════════════════════════════════════════════════════════════
            // 🎯 SIMD 优化的位置操作
            // ═══════════════════════════════════════════════════════════════
            
            // 从缓存数组获取位置(VectorRegister = 4个float,SIMD友好)
            IRISCORE_API VectorRegister GetLocation(const FObjectLocationInfo& Info) const;
            
            // 设置位置到缓存数组
            IRISCORE_API void SetLocation(const FObjectLocationInfo& Info, VectorRegister Location);
            
            // 更新位置(根据来源选择读取方式)
            IRISCORE_API void UpdateLocation(const uint32 ObjectIndex, 
                                              const FObjectLocationInfo& Info, 
                                              const UE::Net::FReplicationInstanceProtocol* InstanceProtocol);
        
        private:
            // ═══════════════════════════════════════════════════════════════
            // 📊 内部数据结构
            // ═══════════════════════════════════════════════════════════════
            
            // 分块存储位置数据,缓存友好
            // LocationsChunkSize = 4096 字节,每块约 256 个位置
            TChunkedArray<VectorRegister, LocationsChunkSize> Locations;
            
            // 位数组,标记哪些位置索引已被分配
            UE::Net::FNetBitArray AssignedLocationIndices;
            
            // 全局位置管理器的引用
            const UE::Net::FWorldLocations* WorldLocations = nullptr;
            
            // 常量定义
            static constexpr uint16 InvalidStateIndex = 0xFFFF;
            static constexpr uint16 InvalidStateOffset = 0xFFFF;
        };

        位置来源的两种方式深入解析:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📍 位置数据来源详解                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  方式1:WorldLocations 系统(推荐)                            │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  适用场景:                                              │  │
        │  │  • 大量对象需要位置信息                                  │  │
        │  │  • 位置更新集中管理                                      │  │
        │  │  • 对象可能没有 RepTag_WorldLocation 标签                │  │
        │  │                                                          │  │
        │  │  工作原理:                                              │  │
        │  │  1. 游戏代码调用 WorldLocations->SetWorldLocation()     │  │
        │  │  2. LocationBasedPrioritizer 从 WorldLocations 读取     │  │
        │  │  3. 位置存储在 WorldLocations 的内部数组中              │  │
        │  │                                                          │  │
        │  │  判断条件:                                              │  │
        │  │  WorldLocations->HasInfoForObject(ObjectIndex) == true  │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  方式2:状态中的 RepTag_WorldLocation                          │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  适用场景:                                              │  │
        │  │  • 位置是复制属性的一部分                                │  │
        │  │  • 需要精确同步的位置                                    │  │
        │  │  • 使用 UPROPERTY(Replicated) FVector Location          │  │
        │  │                                                          │  │
        │  │  工作原理:                                              │  │
        │  │  1. 属性标记为 RepTag_WorldLocation                     │  │
        │  │  2. 系统通过 FindRepTag() 找到位置在状态中的偏移       │  │
        │  │  3. 直接从状态缓冲区读取位置数据                        │  │
        │  │                                                          │  │
        │  │  判断条件:                                              │  │
        │  │  FindRepTag(Protocol, RepTag_WorldLocation, TagInfo)    │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        完整的 AddObject 实现:

        CPP
        // 📍 源文件:LocationBasedNetObjectPrioritizer.cpp
        
        bool ULocationBasedNetObjectPrioritizer::AddObject(uint32 ObjectIndex, 
            FNetObjectPrioritizerAddObjectParams& Params){
            // 支持两种位置来源:WorldLocations 或状态中的 RepTag_WorldLocation 标签
            UE::Net::FRepTagFindInfo TagInfo;
            bool bHasWorldLocation = false;
            
            // ═══════════════════════════════════════════════════════════════
            // 步骤1:确定位置来源
            // ═══════════════════════════════════════════════════════════════
            
            // 优先检查 WorldLocations(更高效)
            if (WorldLocations->HasInfoForObject(ObjectIndex))
            {
                bHasWorldLocation = true;
                // 设置特殊标记,表示从 WorldLocations 获取位置
                TagInfo.StateIndex = InvalidStateIndex;
                TagInfo.ExternalStateOffset = InvalidStateOffset;
            }
            // 其次检查 RepTag_WorldLocation 标签
            else if (!UE::Net::FindRepTag(Params.Protocol, UE::Net::RepTag_WorldLocation, TagInfo))
            {
                // 两种来源都没有,此优先级不支持该对象
                return false;
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 步骤2:验证偏移范围(uint16 限制)
            // ═══════════════════════════════════════════════════════════════
            if (!bHasWorldLocation && 
                ((TagInfo.ExternalStateOffset >= MAX_uint16) || (TagInfo.StateIndex >= MAX_uint16)))
            {
                // 偏移超出 uint16 范围,无法存储
                return false;
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 步骤3:存储位置信息到 FObjectLocationInfo
            // ═══════════════════════════════════════════════════════════════
            FObjectLocationInfo& ObjectInfo = static_cast<FObjectLocationInfo&>(Params.OutInfo);
            ObjectInfo.SetLocationStateOffset(static_cast<uint16>(TagInfo.ExternalStateOffset));
            ObjectInfo.SetLocationStateIndex(static_cast<uint16>(TagInfo.StateIndex));
            
            // ═══════════════════════════════════════════════════════════════
            // 步骤4:分配位置索引并初始化位置
            // ═══════════════════════════════════════════════════════════════
            const uint32 LocationIndex = AllocLocation();
            ObjectInfo.SetLocationIndex(LocationIndex);
            
            // 立即更新位置,确保有初始值
            UpdateLocation(ObjectIndex, ObjectInfo, Params.InstanceProtocol);
        
            return true;
        }

        UpdateLocation 的完整实现:

        CPP
        // 📍 源文件:LocationBasedNetObjectPrioritizer.cpp
        
        void ULocationBasedNetObjectPrioritizer::UpdateLocation(
            const uint32 ObjectIndex, 
            const FObjectLocationInfo& Info, 
            const UE::Net::FReplicationInstanceProtocol* InstanceProtocol){
            if (Info.IsUsingWorldLocations())
            {
                // ═══════════════════════════════════════════════════════════════
                // 方式1:从 WorldLocations 系统获取位置
                // ═══════════════════════════════════════════════════════════════
                const FVector WorldLocation = WorldLocations->GetWorldLocation(ObjectIndex);
                // VectorLoadFloat3_W0: 加载 XYZ,W 设为 0(SIMD 友好)
                SetLocation(Info, VectorLoadFloat3_W0(&WorldLocation));
            }
            else
            {
                // ═══════════════════════════════════════════════════════════════
                // 方式2:从状态缓冲区中读取位置(RepTag_WorldLocation)
                // ═══════════════════════════════════════════════════════════════
                
                // 获取 Fragment 数据数组
                TArrayView<const UE::Net::FReplicationInstanceProtocol::FFragmentData> FragmentDatas = 
                    MakeArrayView(InstanceProtocol->FragmentData, InstanceProtocol->FragmentCount);
                
                // 根据状态索引获取对应的 Fragment
                const UE::Net::FReplicationInstanceProtocol::FFragmentData& FragmentData = 
                    FragmentDatas[Info.GetLocationStateIndex()];
                
                // 计算位置在缓冲区中的地址
                const uint8* LocationOffset = FragmentData.ExternalSrcBuffer + Info.GetLocationStateOffset();
                
                // 直接读取 FVector(假设内存布局兼容)
                SetLocation(Info, VectorLoadFloat3_W0(reinterpret_cast<const FVector*>(LocationOffset)));
            }
        }

        位置分配和释放:

        CPP
        // 📍 源文件:LocationBasedNetObjectPrioritizer.cpp
        
        uint32 ULocationBasedNetObjectPrioritizer::AllocLocation(){
            // 查找第一个未使用的位置索引
            uint32 Index = AssignedLocationIndices.FindFirstZero();
            
            if (Index >= uint32(Locations.Num()))
            {
                // 需要扩展 Locations 数组
                // NumElementsPerChunk = LocationsChunkSize / sizeof(VectorRegister) = 4096 / 16 = 256
                constexpr int32 NumElementsPerChunk = LocationsChunkSize / sizeof(VectorRegister);
                Locations.Add(NumElementsPerChunk);
            }
        
            // 标记该索引已被使用
            AssignedLocationIndices.SetBit(Index);
            return Index;
        }
        
        void ULocationBasedNetObjectPrioritizer::FreeLocation(uint32 Index){
            // 简单地清除标记,位置数据可以被复用
            AssignedLocationIndices.ClearBit(Index);
        }
        
        // GetLocation/SetLocation 非常简单VectorRegister ULocationBasedNetObjectPrioritizer::GetLocation(const FObjectLocationInfo& Info) const{
            return Locations[Info.GetLocationIndex()];
        }
        
        void ULocationBasedNetObjectPrioritizer::SetLocation(const FObjectLocationInfo& Info, VectorRegister Location){
            Locations[Info.GetLocationIndex()] = Location;
        }

        🔵 SphereNetObjectPrioritizer(球形距离优先级)

        "离我越近,越重要!" 🎯

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔵 球形优先级器原理                                │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │              ┌─────────────────────┐                           │
        │              │    外球 (OuterRadius)│                          │
        │              │  ┌───────────────┐  │                           │
        │              │  │ 内球 (Inner)  │  │                           │
        │              │  │   ┌─────┐    │  │                           │
        │              │  │   │ 👤  │    │  │  ← 玩家位置               │
        │              │  │   └─────┘    │  │                           │
        │              │  │  优先级=1.0  │  │  ← 内球:最高优先级       │
        │              │  └───────────────┘  │                           │
        │              │    优先级=0.2~1.0   │  ← 外球:线性衰减         │
        │              └─────────────────────┘                           │
        │                  优先级=0.1          ← 球外:最低优先级        │
        │                                                                │
        │  📐 优先级计算公式(线性插值):                               │
        │  Distance = |ObjPos - ViewPos|                                 │
        │  ClampedDist = Clamp(Distance, InnerRadius, OuterRadius)       │
        │  Factor = (ClampedDist - InnerRadius) / (OuterRadius - InnerRadius)│
        │  Priority = InnerPriority + Factor * (OuterPriority - InnerPriority)│
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        球形优先级系统示意图:内球(绿色)优先级最高,外球(橙色)线性衰减,球外优先级最低

        配置类:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/SphereNetObjectPrioritizer.h
        
        UCLASS(transient, config=Engine, MinimalAPI)
        class USphereNetObjectPrioritizerConfig : public UNetObjectPrioritizerConfig
        {
            GENERATED_BODY()
        
        public:
            UPROPERTY(Config) 
            float InnerRadius = 2000.0f;      // 🔴 内球半径(20米),内部优先级最高
            
            UPROPERTY(Config) 
            float OuterRadius = 10000.0f;     // 🟡 外球半径(100米),边界优先级
            
            UPROPERTY(Config) 
            float InnerPriority = 1.0f;       // 📊 内球内的优先级
            
            UPROPERTY(Config) 
            float OuterPriority = 0.2f;       // 📊 外球边界的优先级
            
            UPROPERTY(Config) 
            float OutsidePriority = 0.1f;     // 📊 球外的优先级
        };

        完整的 Prioritize 实现(批处理架构):

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        void USphereNetObjectPrioritizer::Prioritize(FNetObjectPrioritizationParams& PrioritizationParams){
            IRIS_PROFILER_SCOPE(USphereNetObjectPrioritizer_Prioritize);
            
            // 使用线程局部内存栈,避免堆分配
            FMemStack& Mem = FMemStack::Get();
            FMemMark MemMark(Mem);
        
            // 批处理大小:权衡内存使用和性能
            // 1024 是经过调优的值,既能充分利用缓存,又不会占用太多栈空间
            constexpr uint32 MaxBatchObjectCount = 1024U;
        
            // 确保批大小是 4 的倍数(SIMD 要求)
            uint32 BatchObjectCount = FMath::Min(
                (PrioritizationParams.ObjectCount + 3U) & ~3U,  // 向上取整到 4 的倍数
                MaxBatchObjectCount);
            
            // 设置批处理参数(分配临时数组)
            FBatchParams BatchParams;
            SetupBatchParams(BatchParams, PrioritizationParams, BatchObjectCount, Mem);
        
            // 分批处理所有对象
            for (uint32 ObjectIt = 0, ObjectEndIt = PrioritizationParams.ObjectCount; 
                 ObjectIt < ObjectEndIt; )
            {
                const uint32 CurrentBatchObjectCount = FMath::Min(
                    ObjectEndIt - ObjectIt, MaxBatchObjectCount);
        
                BatchParams.ObjectCount = CurrentBatchObjectCount;
                
                // 1. 准备批次:复制优先级和位置到本地数组
                PrepareBatch(BatchParams, PrioritizationParams, ObjectIt);
                
                // 2. 计算优先级(根据视图数量选择优化路径)
                PrioritizeBatch(BatchParams);
                
                // 3. 完成批次:将结果写回全局数组
                FinishBatch(BatchParams, PrioritizationParams, ObjectIt);
        
                ObjectIt += CurrentBatchObjectCount;
            }
        }

        批处理参数设置:

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        void USphereNetObjectPrioritizer::SetupBatchParams(
            FBatchParams& OutBatchParams, 
            const FNetObjectPrioritizationParams& PrioritizationParams, 
            uint32 MaxBatchObjectCount, 
            FMemStackBase& Mem){
            OutBatchParams.View = PrioritizationParams.View;
            OutBatchParams.ConnectionId = PrioritizationParams.ConnectionId;
            
            // 从内存栈分配临时数组(比堆分配快得多)
            OutBatchParams.Positions = static_cast<VectorRegister*>(
                Mem.Alloc(MaxBatchObjectCount * sizeof(VectorRegister), alignof(VectorRegister)));
            OutBatchParams.Priorities = static_cast<float*>(
                Mem.Alloc(MaxBatchObjectCount * sizeof(float), 16));  // 16字节对齐,SIMD 友好
        
            // 预计算优先级计算常量(避免每次计算时重复)
            SetupCalculationConstants(OutBatchParams.PriorityCalculationConstants);
        
            // 清零位置数组(防止未初始化数据影响 SIMD 计算)
            FMemory::Memzero(OutBatchParams.Positions, MaxBatchObjectCount * sizeof(VectorRegister));
        }
        
        void USphereNetObjectPrioritizer::SetupCalculationConstants(FPriorityCalculationConstants& OutConstants){
            // 将配置值转换为 SIMD 向量(每个分量都是相同的值)
            const VectorRegister InnerRadius = VectorSetFloat1(Config->InnerRadius);
            const VectorRegister OuterRadius = VectorSetFloat1(Config->OuterRadius);
            const VectorRegister InnerPriority = VectorSetFloat1(Config->InnerPriority);
            const VectorRegister OuterPriority = VectorSetFloat1(Config->OuterPriority);
            const VectorRegister OutsidePriority = VectorSetFloat1(Config->OutsidePriority);
        
            // 预计算差值(避免运行时重复计算)
            const VectorRegister RadiusDiff = VectorSubtract(OuterRadius, InnerRadius);
            const VectorRegister PriorityDiff = VectorSubtract(OuterPriority, InnerPriority);
        
            OutConstants.InnerRadius = InnerRadius;
            OutConstants.OuterRadius = OuterRadius;
            OutConstants.RadiusDiff = RadiusDiff;
            OutConstants.InvRadiusDiff = VectorReciprocalAccurate(RadiusDiff);  // 1 / RadiusDiff
            OutConstants.InnerPriority = InnerPriority;
            OutConstants.OuterPriority = OuterPriority;
            OutConstants.OutsidePriority = OutsidePriority;
            OutConstants.PriorityDiff = PriorityDiff;
        }

        视图数量分发:

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        void USphereNetObjectPrioritizer::PrioritizeBatch(FBatchParams& BatchParams){
            const int32 ViewCount = BatchParams.View.Views.Num();
            
            // 根据视图数量选择最优化的代码路径
            if (ViewCount == 1)
            {
                PrioritizeBatchForSingleView(BatchParams);    // 单视图:最常见,最优化
            }
            else if (ViewCount == 2)
            {
                PrioritizeBatchForDualView(BatchParams);      // 双视图:分屏游戏优化
            }
            else
            {
                PrioritizeBatchForMultiView(BatchParams);     // 多视图:通用但较慢
            }
        }

        单视图 SIMD 优化实现(核心算法):

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        /**
         * 优先级线性衰减公式:
         * Priority = OuterPriority + (OuterPriority - InnerPriority) * 
         *     (Clamp(Distance(ObjPos, ViewPos), InnerRadius, OuterRadius) / (OuterRadius - InnerRadius))
         */void USphereNetObjectPrioritizer::PrioritizeBatchForSingleView(FBatchParams& BatchParams){
            IRIS_PROFILER_SCOPE(USphereNetObjectPrioritizer_PrioritizeBatchForSingleView);
            
            // 加载视图位置到 SIMD 寄存器
            const FVector& ViewPosVector = BatchParams.View.Views[0].Pos;
            const VectorRegister ViewPos = VectorLoadFloat3_W0(&ViewPosVector);
        
            const VectorRegister* Positions = BatchParams.Positions;
            float* Priorities = BatchParams.Priorities;
            
            // ═══════════════════════════════════════════════════════════════
            // 每次处理 4 个对象(SIMD 4-way 并行)
            // ═══════════════════════════════════════════════════════════════
            for (uint32 ObjIt = 0, ObjEndIt = BatchParams.ObjectCount; ObjIt < ObjEndIt; ObjIt += 4)
            {
                // 步骤1:加载 4 个对象的位置
                const VectorRegister Pos0 = Positions[ObjIt + 0];
                const VectorRegister Pos1 = Positions[ObjIt + 1];
                const VectorRegister Pos2 = Positions[ObjIt + 2];
                const VectorRegister Pos3 = Positions[ObjIt + 3];
        
                // 加载原始优先级(用于 max 操作)
                const VectorRegister OriginalPriorities0123 = VectorLoadAligned(Priorities + ObjIt);
        
                // 步骤2:计算到视图中心的距离向量
                const VectorRegister Dist0 = VectorSubtract(Pos0, ViewPos);
                const VectorRegister Dist1 = VectorSubtract(Pos1, ViewPos);
                const VectorRegister Dist2 = VectorSubtract(Pos2, ViewPos);
                const VectorRegister Dist3 = VectorSubtract(Pos3, ViewPos);
        
                // 步骤3:计算距离平方(点积 = x² + y² + z²)
                const VectorRegister ScalarDistSqr0 = VectorDot4(Dist0, Dist0);
                const VectorRegister ScalarDistSqr1 = VectorDot4(Dist1, Dist1);
                const VectorRegister ScalarDistSqr2 = VectorDot4(Dist2, Dist2);
                const VectorRegister ScalarDistSqr3 = VectorDot4(Dist3, Dist3);
        
                // 步骤4:组装 4 个距离到单个向量
                // $IRIS TODO: 可用 SSE 4.1 _mm_blend_ps 优化
                const VectorRegister ScalarDistSqr0101 = VectorSwizzle(
                    VectorCombineHigh(ScalarDistSqr0, ScalarDistSqr1), 0, 2, 1, 3);
                const VectorRegister ScalarDistSqr2323 = VectorSwizzle(
                    VectorCombineHigh(ScalarDistSqr2, ScalarDistSqr3), 0, 2, 1, 3);
                const VectorRegister ScalarDistSqr0123 = VectorCombineHigh(ScalarDistSqr0101, ScalarDistSqr2323);
        
                // 步骤5:计算实际距离(开方)
                const VectorRegister ScalarDist0123 = VectorSqrt(ScalarDistSqr0123);
                
                // 步骤6:Clamp 到 [0, OuterRadius - InnerRadius] 范围
                // ClampedDist = max(Distance - InnerRadius, 0)
                const VectorRegister ClampedScalarDist0123 = VectorMax(
                    VectorSubtract(ScalarDist0123, BatchParams.PriorityCalculationConstants.InnerRadius), 
                    VectorZeroVectorRegister());
        
                // 步骤7:计算优先级(假设在球内)
                // Factor = ClampedDist / RadiusDiff
                const VectorRegister RadiusFactor = VectorMultiply(
                    ClampedScalarDist0123, 
                    BatchParams.PriorityCalculationConstants.InvRadiusDiff);
                
                // Priority = InnerPriority + Factor * PriorityDiff
                VectorRegister Priorities0123 = VectorMultiplyAdd(
                    RadiusFactor, 
                    BatchParams.PriorityCalculationConstants.PriorityDiff, 
                    BatchParams.PriorityCalculationConstants.InnerPriority);
        
                // 步骤8:处理球外情况
                // 如果 ClampedDist > RadiusDiff,则使用 OutsidePriority
                const VectorRegister OutsideSphereMask = VectorCompareGT(
                    ClampedScalarDist0123, 
                    BatchParams.PriorityCalculationConstants.RadiusDiff);
                Priorities0123 = VectorSelect(
                    OutsideSphereMask, 
                    BatchParams.PriorityCalculationConstants.OutsidePriority, 
                    Priorities0123);
        
                // 步骤9:取计算值和原始值的最大值
                // 这允许其他系统预设更高的优先级
                Priorities0123 = VectorMax(Priorities0123, OriginalPriorities0123);
                
                // 步骤10:存储结果
                VectorStoreAligned(Priorities0123, Priorities + ObjIt);
            }
        }

        双视图实现(分屏游戏优化):

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        void USphereNetObjectPrioritizer::PrioritizeBatchForDualView(FBatchParams& BatchParams){
            IRIS_PROFILER_SCOPE(USphereNetObjectPrioritizer_PrioritizeBatchForDualView);
            
            // 加载两个视图位置
            const FVector& ViewPos0Vector = BatchParams.View.Views[0].Pos;
            const VectorRegister ViewPos0 = VectorLoadFloat3_W0(&ViewPos0Vector);
        
            const FVector& ViewPos1Vector = BatchParams.View.Views[1].Pos;
            const VectorRegister ViewPos1 = VectorLoadFloat3_W0(&ViewPos1Vector);
        
            const VectorRegister* Positions = BatchParams.Positions;
            float* Priorities = BatchParams.Priorities;
            
            for (uint32 ObjIt = 0, ObjEndIt = BatchParams.ObjectCount; ObjIt < ObjEndIt; ObjIt += 4)
            {
                const VectorRegister Pos0 = Positions[ObjIt + 0];
                const VectorRegister Pos1 = Positions[ObjIt + 1];
                const VectorRegister Pos2 = Positions[ObjIt + 2];
                const VectorRegister Pos3 = Positions[ObjIt + 3];
        
                const VectorRegister OriginalPriorities0123 = VectorLoadAligned(Priorities + ObjIt);
        
                // ═══════════════════════════════════════════════════════════════
                // 计算到第一个视图的距离
                // ═══════════════════════════════════════════════════════════════
                const VectorRegister Dist0_0 = VectorSubtract(Pos0, ViewPos0);
                const VectorRegister Dist1_0 = VectorSubtract(Pos1, ViewPos0);
                const VectorRegister Dist2_0 = VectorSubtract(Pos2, ViewPos0);
                const VectorRegister Dist3_0 = VectorSubtract(Pos3, ViewPos0);
        
                VectorRegister ScalarDistSqr0 = VectorDot4(Dist0_0, Dist0_0);
                VectorRegister ScalarDistSqr1 = VectorDot4(Dist1_0, Dist1_0);
                VectorRegister ScalarDistSqr2 = VectorDot4(Dist2_0, Dist2_0);
                VectorRegister ScalarDistSqr3 = VectorDot4(Dist3_0, Dist3_0);
        
                // ═══════════════════════════════════════════════════════════════
                // 计算到第二个视图的距离
                // ═══════════════════════════════════════════════════════════════
                const VectorRegister Dist0_1 = VectorSubtract(Pos0, ViewPos1);
                const VectorRegister Dist1_1 = VectorSubtract(Pos1, ViewPos1);
                const VectorRegister Dist2_1 = VectorSubtract(Pos2, ViewPos1);
                const VectorRegister Dist3_1 = VectorSubtract(Pos3, ViewPos1);
        
                const VectorRegister ScalarDistSqr0_1 = VectorDot4(Dist0_1, Dist0_1);
                const VectorRegister ScalarDistSqr1_1 = VectorDot4(Dist1_1, Dist1_1);
                const VectorRegister ScalarDistSqr2_1 = VectorDot4(Dist2_1, Dist2_1);
                const VectorRegister ScalarDistSqr3_1 = VectorDot4(Dist3_1, Dist3_1);
        
                // ═══════════════════════════════════════════════════════════════
                // 🔑 关键:选择最近的视图距离(取最小值)
                // ═══════════════════════════════════════════════════════════════
                ScalarDistSqr0 = VectorMin(ScalarDistSqr0, ScalarDistSqr0_1);
                ScalarDistSqr1 = VectorMin(ScalarDistSqr1, ScalarDistSqr1_1);
                ScalarDistSqr2 = VectorMin(ScalarDistSqr2, ScalarDistSqr2_1);
                ScalarDistSqr3 = VectorMin(ScalarDistSqr3, ScalarDistSqr3_1);
        
                // 后续计算与单视图相同...
                // (组装距离、开方、Clamp、计算优先级、处理球外、取最大值、存储)
            }
        }

        多视图实现(通用路径):

        CPP
        // 📍 源文件:SphereNetObjectPrioritizer.cpp
        
        void USphereNetObjectPrioritizer::PrioritizeBatchForMultiView(FBatchParams& BatchParams){
            IRIS_PROFILER_SCOPE(USphereNetObjectPrioritizer_PrioritizeBatchForMultiView);
            
            // 预加载所有视图位置(使用内联分配器,最多 8 个视图不需要堆分配)
            TArray<VectorRegister, TInlineAllocator<8>> ViewPositions;
            for (const UE::Net::FReplicationView::FView& View : BatchParams.View.Views)
            {
                const FVector& ViewPosVector = View.Pos;
                ViewPositions.Add(VectorLoadFloat3_W0(&ViewPosVector));
            }
            
            // 性能警告:超过 8 个视图会触发堆分配
            ensureMsgf(ViewPositions.Num() <= 8, 
                TEXT("Performance warning: Global allocation was needed to accommodate %d views."), 
                ViewPositions.Num());
        
            const VectorRegister MaxFloatVector = VectorSetFloat1(MAX_flt);
        
            const VectorRegister* Positions = BatchParams.Positions;
            float* Priorities = BatchParams.Priorities;
            
            for (uint32 ObjIt = 0, ObjEndIt = BatchParams.ObjectCount; ObjIt < ObjEndIt; ObjIt += 4)
            {
                const VectorRegister Pos0 = Positions[ObjIt + 0];
                const VectorRegister Pos1 = Positions[ObjIt + 1];
                const VectorRegister Pos2 = Positions[ObjIt + 2];
                const VectorRegister Pos3 = Positions[ObjIt + 3];
        
                const VectorRegister OriginalPriorities0123 = VectorLoadAligned(Priorities + ObjIt);
        
                // 初始化为最大距离
                VectorRegister ScalarDistSqr0 = MaxFloatVector;
                VectorRegister ScalarDistSqr1 = MaxFloatVector;
                VectorRegister ScalarDistSqr2 = MaxFloatVector;
                VectorRegister ScalarDistSqr3 = MaxFloatVector;
        
                // ═══════════════════════════════════════════════════════════════
                // 遍历所有视图,取最小距离
                // ═══════════════════════════════════════════════════════════════
                for (VectorRegister ViewPos : ViewPositions)
                {
                    const VectorRegister Dist0 = VectorSubtract(Pos0, ViewPos);
                    const VectorRegister Dist1 = VectorSubtract(Pos1, ViewPos);
                    const VectorRegister Dist2 = VectorSubtract(Pos2, ViewPos);
                    const VectorRegister Dist3 = VectorSubtract(Pos3, ViewPos);
        
                    ScalarDistSqr0 = VectorMin(ScalarDistSqr0, VectorDot4(Dist0, Dist0));
                    ScalarDistSqr1 = VectorMin(ScalarDistSqr1, VectorDot4(Dist1, Dist1));
                    ScalarDistSqr2 = VectorMin(ScalarDistSqr2, VectorDot4(Dist2, Dist2));
                    ScalarDistSqr3 = VectorMin(ScalarDistSqr3, VectorDot4(Dist3, Dist3));
                }
        
                // 后续计算与单视图相同...
            }
        }

        👑 SphereWithOwnerBoostNetObjectPrioritizer(所有者加成)

        "自己的东西,当然更重要!" 👑

        CPP
        // 📍 源文件:SphereWithOwnerBoostNetObjectPrioritizer.h
        
        UCLASS(Transient, Config=Engine)
        class USphereWithOwnerBoostNetObjectPrioritizerConfig 
            : public USphereNetObjectPrioritizerConfig  // 继承球形配置
        {
        public:
            UPROPERTY(Config)
            float OwnerPriorityBoost = 2.0f;  // 👑 所有者优先级加成
        };
        PLAINTEXT
        对玩家A来说:
          自己的宠物🐕 优先级 = 距离优先级(0.5) + 所有者加成(2.0) = 2.5
          
        对玩家B来说:
          别人的宠物🐕 优先级 = 距离优先级(0.5) + 0 = 0.5

        👁️ FieldOfViewNetObjectPrioritizer(视野优先级)

        "我正在看的方向,更重要!" 👁️

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              👁️ 视野优先级器原理                               │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │                        视野锥 (Cone)                           │
        │                       ╱           ╲                            │
        │                      ╱  高优先级   ╲                           │
        │                     ╱    🎯  🎯     ╲                          │
        │                    ╱                 ╲                         │
        │                   ╱         👤        ╲                        │
        │                  ╱      (玩家位置)     ╲                       │
        │                 ╱   ┌─────────────┐    ╲                      │
        │                ╱    │  内球(高)   │     ╲                     │
        │               ╱     └─────────────┘      ╲                    │
        │              ╱          外球(中)          ╲                   │
        │             ╱                              ╲                  │
        │            ╱        视线胶囊(最高)          ╲                 │
        │           ╱         ════════════            ╲                │
        │                                                                │
        │  📊 优先级区域(按优先级从高到低):                           │
        │  1. 视线胶囊 (Line of Sight) - 准星正对方向                   │
        │  2. 内球 (Inner Sphere) - 近距离 360°                         │
        │  3. 视野锥 (Cone) - 前方视野范围                              │
        │  4. 外球 (Outer Sphere) - 中距离 360°                         │
        │  5. 外部 (Outside) - 所有其他位置                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        视野优先级系统示意图:视线胶囊(红色)优先级最高,内球(绿色)次之,视野锥(紫色)中高,外球(橙色)中等

        完整配置类:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/FieldOfViewNetObjectPrioritizer.h
        
        UCLASS(transient, config=Engine, MinimalAPI)
        class UFieldOfViewNetObjectPrioritizerConfig : public UNetObjectPrioritizerConfig
        {
            GENERATED_BODY()
        
        public:
            // ═══════════════════════════════════════════════════════════════
            // 🔵 内球参数(近距离 360° 高优先级)
            // ═══════════════════════════════════════════════════════════════
            UPROPERTY(Config) 
            float InnerSphereRadius = 3000.0f;      // 内球半径(30米)
            
            UPROPERTY(Config) 
            float InnerSpherePriority = 1.0f;       // 内球优先级
            
            // ═══════════════════════════════════════════════════════════════
            // 🟡 外球参数(中距离 360° 中等优先级)
            // ═══════════════════════════════════════════════════════════════
            UPROPERTY(Config) 
            float OuterSphereRadius = 10000.0f;     // 外球半径(100米)
            
            UPROPERTY(Config) 
            float OuterSpherePriority = 0.2f;       // 外球优先级
            
            // ═══════════════════════════════════════════════════════════════
            // 📐 视野锥参数(前方视野范围)
            // ═══════════════════════════════════════════════════════════════
            UPROPERTY(Config) 
            float ConeFieldOfViewDegrees = 45.0f;   // 视野角度(单侧,总角度 90°)
            
            UPROPERTY(Config) 
            float InnerConeLength = 3000.0f;        // 内锥长度(30米内最高优先级)
            
            UPROPERTY(Config) 
            float ConeLength = 20000.0f;            // 锥体总长度(200米)
            
            UPROPERTY(Config) 
            float MinConePriority = 0.2f;           // 锥体边缘优先级
            
            UPROPERTY(Config) 
            float MaxConePriority = 1.0f;           // 锥体中心优先级
            
            // ═══════════════════════════════════════════════════════════════
            // 🎯 视线参数(准星正对方向)
            // ═══════════════════════════════════════════════════════════════
            UPROPERTY(Config) 
            float LineOfSightWidth = 200.0f;        // 视线宽度(2米直径的胶囊)
            
            UPROPERTY(Config) 
            float LineOfSightPriority = 1.0f;       // 视线优先级(最高)
            
            // ═══════════════════════════════════════════════════════════════
            // ⬛ 外部优先级
            // ═══════════════════════════════════════════════════════════════
            UPROPERTY(Config) 
            float OutsidePriority = 0.1f;           // 所有区域外的优先级
        };

        计算常量设置(预计算优化):

        CPP
        // 📍 源文件:FieldOfViewNetObjectPrioritizer.cpp
        
        void UFieldOfViewNetObjectPrioritizer::SetupCalculationConstants(FPriorityCalculationConstants& OutConstants){
            // ═══════════════════════════════════════════════════════════════
            // 锥体常量
            // ═══════════════════════════════════════════════════════════════
            VectorRegister InnerConeLength = VectorSetFloat1(Config->InnerConeLength);
            VectorRegister ConeLength = VectorSetFloat1(Config->ConeLength);
            VectorRegister ConeLengthDiff = VectorSubtract(ConeLength, InnerConeLength);
            VectorRegister InvConeLengthDiff = VectorReciprocalAccurate(ConeLengthDiff);
            
            // 计算锥体半径:ConeLength * tan(FOV/2)
            VectorRegister ConeRadius = VectorSetFloat1(
                Config->ConeLength * FMath::Tan(0.5f * FMath::DegreesToRadians(Config->ConeFieldOfViewDegrees)));
            
            // 锥体半径因子:用于计算任意距离处的锥体半径
            // RadiusAtDist = Dist * ConeRadiusFactor
            VectorRegister ConeRadiusFactor = VectorDivide(ConeRadius, ConeLength);
            
            VectorRegister InnerConePriority = VectorSetFloat1(Config->MaxConePriority); 
            VectorRegister OuterConePriority = VectorSetFloat1(Config->MinConePriority);
            VectorRegister ConePriorityDiff = VectorSubtract(OuterConePriority, InnerConePriority);
        
            // ═══════════════════════════════════════════════════════════════
            // 球体常量(使用半径平方避免开方)
            // ═══════════════════════════════════════════════════════════════
            VectorRegister InnerSphereRadiusSqr = VectorSetFloat1(FMath::Square(Config->InnerSphereRadius));
            VectorRegister OuterSphereRadiusSqr = VectorSetFloat1(FMath::Square(Config->OuterSphereRadius));
            VectorRegister InnerSpherePriority = VectorSetFloat1(Config->InnerSpherePriority);
            VectorRegister OuterSpherePriority = VectorSetFloat1(Config->OuterSpherePriority);
        
            // ═══════════════════════════════════════════════════════════════
            // 视线常量
            // ═══════════════════════════════════════════════════════════════
            // 视线是一个胶囊体,半径 = Width/2
            VectorRegister LineOfSightRadiusSqr = VectorSetFloat1(FMath::Square(0.5f * Config->LineOfSightWidth));
            VectorRegister LineOfSightPriority = VectorSetFloat1(Config->LineOfSightPriority);
        
            // ═══════════════════════════════════════════════════════════════
            // 外部优先级
            // ═══════════════════════════════════════════════════════════════
            VectorRegister OutsidePriority = VectorSetFloat1(Config->OutsidePriority);
        
            // 存储所有常量...
        }

        完整的 PrioritizeBatch 实现:

        CPP
        // 📍 源文件:FieldOfViewNetObjectPrioritizer.cpp
        
        void UFieldOfViewNetObjectPrioritizer::PrioritizeBatch(FBatchParams& BatchParams){
            IRIS_PROFILER_SCOPE(UFieldOfViewNetObjectPrioritizer_PrioritizeBatch);
        
            // 预加载所有视图位置和方向
            // 注意:锥体和视线依赖视图方向,无法像球体那样只用最近距离优化
            TArray<VectorRegister, TInlineAllocator<16>> ViewPositions;
            TArray<VectorRegister, TInlineAllocator<16>> ViewDirs;
            for (const UE::Net::FReplicationView::FView& View : BatchParams.View.Views)
            {
                const FVector& ViewPos = View.Pos;
                const FVector& ViewDir = View.Dir;
                ViewPositions.Add(VectorLoadFloat3_W0(&ViewPos));
                ViewDirs.Add(VectorLoadFloat3_W0(&ViewDir));
            }
        
            const VectorRegister* Positions = BatchParams.Positions;
            float* Priorities = BatchParams.Priorities;
            const int ViewCount = BatchParams.View.Views.Num();
            
            for (uint32 ObjIt = 0, ObjEndIt = BatchParams.ObjectCount; ObjIt < ObjEndIt; ObjIt += 4)
            {
                // 初始化为外部优先级(取原始值和外部优先级的最大值)
                VectorRegister Priorities0123 = VectorMax(
                    VectorLoadAligned(Priorities + ObjIt), 
                    BatchParams.PriorityCalculationConstants.OutsidePriority);
                
                // ═══════════════════════════════════════════════════════════════
                // 遍历所有视图(锥体和视线依赖视图方向,必须逐个处理)
                // ═══════════════════════════════════════════════════════════════
                for (int ViewIt = 0, ViewEndIt = ViewCount; ViewIt < ViewEndIt; ++ViewIt)
                {
                    const VectorRegister ViewPos = ViewPositions[ViewIt];
                    const VectorRegister ViewDir = ViewDirs[ViewIt];
        
                    // 步骤1:计算对象方向向量(从视图到对象)
                    const VectorRegister ObjectDir0 = VectorSubtract(Positions[ObjIt + 0], ViewPos);
                    const VectorRegister ObjectDir1 = VectorSubtract(Positions[ObjIt + 1], ViewPos);
                    const VectorRegister ObjectDir2 = VectorSubtract(Positions[ObjIt + 2], ViewPos);
                    const VectorRegister ObjectDir3 = VectorSubtract(Positions[ObjIt + 3], ViewPos);
        
                    // 步骤2:计算到视图位置的距离平方
                    const VectorRegister DistSqrToViewPos0 = VectorDot4(ObjectDir0, ObjectDir0);
                    const VectorRegister DistSqrToViewPos1 = VectorDot4(ObjectDir1, ObjectDir1);
                    const VectorRegister DistSqrToViewPos2 = VectorDot4(ObjectDir2, ObjectDir2);
                    const VectorRegister DistSqrToViewPos3 = VectorDot4(ObjectDir3, ObjectDir3);
        
                    // 组装距离向量
                    const VectorRegister DistSqrToViewPos0101 = VectorSwizzle(
                        VectorCombineHigh(DistSqrToViewPos0, DistSqrToViewPos1), 0, 2, 1, 3);
                    const VectorRegister DistSqrToViewPos2323 = VectorSwizzle(
                        VectorCombineHigh(DistSqrToViewPos2, DistSqrToViewPos3), 0, 2, 1, 3);
                    const VectorRegister DistSqrToViewPos0123 = VectorCombineHigh(
                        DistSqrToViewPos0101, DistSqrToViewPos2323);
                    const VectorRegister DistToViewPos0123 = VectorSqrt(DistSqrToViewPos0123);
        
                    // 步骤3:投影对象方向到锥体中心轴(点积 = 沿视线方向的距离)
                    const VectorRegister ConeDist0 = VectorDot4(ObjectDir0, ViewDir);
                    const VectorRegister ConeDist1 = VectorDot4(ObjectDir1, ViewDir);
                    const VectorRegister ConeDist2 = VectorDot4(ObjectDir2, ViewDir);
                    const VectorRegister ConeDist3 = VectorDot4(ObjectDir3, ViewDir);
        
                    // 步骤4:计算到锥体中心轴的距离平方
                    // 投影点 = ViewPos + ConeDist * ViewDir
                    // 轴距离向量 = ObjPos - 投影点 = ObjectDir - ConeDist * ViewDir
                    VectorRegister DistSqrToConeCenterAxis0 = VectorSubtract(
                        ObjectDir0, VectorMultiply(ConeDist0, ViewDir));
                    DistSqrToConeCenterAxis0 = VectorDot4(DistSqrToConeCenterAxis0, DistSqrToConeCenterAxis0);
                    // ... 其他3个类似 ...
        
                    // 组装锥体距离和轴距离
                    const VectorRegister ConeDist0123 = /* 组装 */;
                    const VectorRegister DistSqrToConeCenterAxis0123 = /* 组装 */;
        
                    // 步骤5:验证锥体距离在有效范围 [0, ConeLength]
                    const VectorRegister ConeDistGEZeroMask = VectorCompareGE(
                        ConeDist0123, VectorZeroVectorRegister());
                    const VectorRegister ConeDistLEDistMask = VectorCompareLE(
                        ConeDist0123, BatchParams.PriorityCalculationConstants.ConeLength);
                    const VectorRegister ConeDistInRangeMask = VectorBitwiseAnd(
                        ConeDistGEZeroMask, ConeDistLEDistMask);
        
                    // 步骤6:计算该距离处的锥体半径平方
                    // RadiusAtDist = ConeDist * ConeRadiusFactor
                    VectorRegister ConeRadiusAtDistSqr = VectorMultiply(
                        ConeDist0123, BatchParams.PriorityCalculationConstants.ConeRadiusFactor);
                    ConeRadiusAtDistSqr = VectorMultiply(ConeRadiusAtDistSqr, ConeRadiusAtDistSqr);
        
                    // ═══════════════════════════════════════════════════════════════
                    // 计算各区域优先级
                    // ═══════════════════════════════════════════════════════════════
        
                    // 🔺 锥体优先级
                    // 条件:在锥体内 && 在有效距离范围内
                    const VectorRegister InsideConeMask = VectorBitwiseAnd(
                        VectorCompareLE(DistSqrToConeCenterAxis0123, ConeRadiusAtDistSqr), 
                        ConeDistInRangeMask);
                    const VectorRegister InsideInnerConeMask = VectorCompareLE(
                        ConeDist0123, BatchParams.PriorityCalculationConstants.InnerConeLength);
                    
                    // 锥体优先级线性插值
                    const VectorRegister ConeLengthFactor = VectorMultiply(
                        VectorSubtract(DistToViewPos0123, 
                            BatchParams.PriorityCalculationConstants.InnerConeLength), 
                        BatchParams.PriorityCalculationConstants.InvConeLengthDiff);
                    VectorRegister ConePriorities0123 = VectorMultiplyAdd(
                        ConeLengthFactor, 
                        BatchParams.PriorityCalculationConstants.ConePriorityDiff, 
                        BatchParams.PriorityCalculationConstants.InnerConePriority);
                    
                    // 内锥体使用最大优先级
                    ConePriorities0123 = VectorSelect(
                        InsideInnerConeMask, 
                        BatchParams.PriorityCalculationConstants.InnerConePriority, 
                        ConePriorities0123);
                    ConePriorities0123 = VectorBitwiseAnd(ConePriorities0123, InsideConeMask);
        
                    // 🎯 视线优先级
                    // 条件:到中心轴距离 <= 视线半径 && 在有效距离范围内
                    const VectorRegister InsideLineOfSightMask = VectorBitwiseAnd(
                        VectorCompareLE(DistSqrToConeCenterAxis0123, 
                            BatchParams.PriorityCalculationConstants.LineOfSightRadiusSqr), 
                        ConeDistInRangeMask);
                    const VectorRegister LoSPriorities0123 = VectorBitwiseAnd(
                        InsideLineOfSightMask, 
                        BatchParams.PriorityCalculationConstants.LineOfSightPriority);
        
                    // 🟡 外球优先级
                    const VectorRegister InsideOuterSphereMask = VectorCompareLE(
                        DistSqrToViewPos0123, 
                        BatchParams.PriorityCalculationConstants.OuterSphereRadiusSqr);
                    const VectorRegister OuterSpherePriorities0123 = VectorBitwiseAnd(
                        InsideOuterSphereMask, 
                        BatchParams.PriorityCalculationConstants.OuterSpherePriority);
        
                    // 🔵 内球优先级
                    const VectorRegister InsideInnerSphereMask = VectorCompareLE(
                        DistSqrToViewPos0123, 
                        BatchParams.PriorityCalculationConstants.InnerSphereRadiusSqr);
                    const VectorRegister InsideSpherePriorities0123 = VectorBitwiseAnd(
                        InsideInnerSphereMask, 
                        BatchParams.PriorityCalculationConstants.InnerSpherePriority);
        
                    // ═══════════════════════════════════════════════════════════════
                    // 🔑 关键:取所有优先级的最大值
                    // ═══════════════════════════════════════════════════════════════
                    Priorities0123 = VectorMax(Priorities0123, 
                        VectorMax(ConePriorities0123, LoSPriorities0123));
                    Priorities0123 = VectorMax(Priorities0123, 
                        VectorMax(OuterSpherePriorities0123, InsideSpherePriorities0123));
                }
        
                // 存储结果
                VectorStoreAligned(Priorities0123, Priorities + ObjIt);
            }
        }

        优先级区域判断逻辑图解:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📐 FOV 优先级区域判断                            │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  对于每个对象,计算以下值:                                     │
        │  • DistToViewPos: 到视图位置的距离                             │
        │  • ConeDist: 沿视线方向的投影距离(点积)                      │
        │  • DistToConeCenterAxis: 到视线中心轴的垂直距离                │
        │                                                                │
        │  判断顺序(取最大优先级):                                     │
        │                                                                │
        │  1. 内球检查                                                   │
        │     if (DistToViewPos² <= InnerSphereRadius²)                  │
        │         Priority = max(Priority, InnerSpherePriority)          │
        │                                                                │
        │  2. 外球检查                                                   │
        │     if (DistToViewPos² <= OuterSphereRadius²)                  │
        │         Priority = max(Priority, OuterSpherePriority)          │
        │                                                                │
        │  3. 视线检查(在有效距离范围内)                               │
        │     if (ConeDist >= 0 && ConeDist <= ConeLength &&             │
        │         DistToConeCenterAxis² <= LineOfSightRadius²)           │
        │         Priority = max(Priority, LineOfSightPriority)          │
        │                                                                │
        │  4. 锥体检查(在有效距离范围内)                               │
        │     ConeRadiusAtDist = ConeDist * tan(FOV/2)                   │
        │     if (ConeDist >= 0 && ConeDist <= ConeLength &&             │
        │         DistToConeCenterAxis² <= ConeRadiusAtDist²)            │
        │         ConePriority = Lerp(MaxConePriority, MinConePriority,  │
        │                             (DistToViewPos - InnerConeLength)  │
        │                             / (ConeLength - InnerConeLength))  │
        │         Priority = max(Priority, ConePriority)                 │
        │                                                                │
        │  📌 注意:所有检查都是独立的,最终取最大值                     │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🔢 NetObjectCountLimiter(对象数量限制器)

        "不管优先级多高,每帧只能处理这么多!" 🔢

        这是一个独立的优先级(不继承自 LocationBasedNetObjectPrioritizer),专门用于限制每帧考虑复制的对象数量。

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔢 CountLimiter 应用场景                           │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  场景:游戏中有 1000 个金币🪙                                   │
        │                                                                │
        │  问题:                                                        │
        │  • 金币数量太多,全部计算优先级太慢                            │
        │  • 金币重要性相近,没必要精确排序                              │
        │  • 玩家不需要同时看到所有金币                                  │
        │                                                                │
        │  解决方案:使用 CountLimiter                                   │
        │  • 每帧只考虑 N 个金币(如 10 个)                             │
        │  • 轮流给每个金币"出镜机会"                                   │
        │  • 防止任何金币被永久"饿死"                                   │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        完整配置类:

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/Prioritization/NetObjectCountLimiter.h
        
        UENUM()
        enum class ENetObjectCountLimiterMode : uint32
        {
            /**
             * RoundRobin 模式:每次网络更新,下 N 个对象将被允许复制(如果有修改)
             * 这意味着即使有很多对象有修改,如果本帧考虑的 N 个对象没有修改,
             * 则不会发送任何数据。
             */
            RoundRobin,
            
            /**
             * Fill 模式:每次网络更新,N 个最久未复制的有修改对象将被允许复制
             * 如果一个对象修改频繁而其他对象不修改,该对象可能会被非常频繁地复制
             */
            Fill,
        };
        
        UCLASS(transient, config=Engine, MinimalAPI)
        class UNetObjectCountLimiterConfig : public UNetObjectPrioritizerConfig
        {
            GENERATED_BODY()
        
        public:
            UPROPERTY()
            ENetObjectCountLimiterMode Mode = ENetObjectCountLimiterMode::RoundRobin;
        
            /**
             * 每帧考虑复制的最大对象数
             * 设为 2 时,至少 1 个非连接拥有的对象会被考虑
             * 如果优先级不处理连接拥有的对象,可以设为 1
             */
            UPROPERTY(Config)
            uint32 MaxObjectCount = 2;
        
            /**
             * 被考虑复制的对象的优先级
             * 优先级会累积直到对象被复制
             * 1.0f 是对象可能被复制的阈值
             */
            UPROPERTY(Config)
            float Priority = 1.0f;
        
            /**
             * 如果对象被当前连接拥有,设置的优先级
             */
            UPROPERTY(Config)
            float OwningConnectionPriority = 1.0f;
        
            /**
             * 连接拥有的对象是否总是被考虑复制
             * 如果是,这些对象不计入 MaxObjectCount
             */
            UPROPERTY(Config)
            bool bEnableOwnedObjectsFastLane = true;
        };

        核心数据结构:

        CPP
        // 📍 源文件:NetObjectCountLimiter.h
        
        UCLASS()
        class UNetObjectCountLimiter : public UNetObjectPrioritizer
        {
            GENERATED_BODY()
        
        protected:
            // ═══════════════════════════════════════════════════════════════
            // 📦 对象信息结构(重新解释 FNetObjectPrioritizationInfo 的 8 字节)
            // ═══════════════════════════════════════════════════════════════
            struct FObjectInfo : public FNetObjectPrioritizationInfo
            {
                // Data[0]: 优先级内部索引
                void SetPrioritizerInternalIndex(uint16 Index) { Data[0] = Index; }
                uint16 GetPrioritizerInternalIndex() const { return Data[0]; }
        
                // Data[1]: 拥有该对象的连接 ID(用于快速通道判断)
                void SetOwningConnection(uint32 ConnectionId) { Data[1] = static_cast<uint16>(ConnectionId); }
                uint32 GetOwningConnection() const { return static_cast<uint32>(Data[1]); }
            };
        
            // ═══════════════════════════════════════════════════════════════
            // 📊 每连接信息(用于 Fill 模式的饥饿追踪)
            // ═══════════════════════════════════════════════════════════════
            struct FPerConnectionInfo
            {
                // 每个对象上次被考虑复制的帧号
                // 用于计算"饥饿度" = 当前帧 - 上次考虑帧
                TArray<uint32> LastConsiderFrames;
            };
        
        private:
            // ═══════════════════════════════════════════════════════════════
            // 🔄 RoundRobin 状态
            // ═══════════════════════════════════════════════════════════════
            struct FRoundRobinState
            {
                UE::Net::FNetBitArray InternalObjectIndices;  // 本帧要考虑的对象
                uint16 NextIndexToConsider = 0;               // 下次开始的索引
            };
        
            // ═══════════════════════════════════════════════════════════════
            // 📥 Fill 状态(用于检测同一帧多次调用)
            // ═══════════════════════════════════════════════════════════════
            struct FFillState
            {
                uint32 LastPrioFrame = 0;       // 上次优先级计算的帧
                uint32 LastConnectionId = 0;    // 上次处理的连接 ID
            };
        
            // 配置和状态
            TStrongObjectPtr<UNetObjectCountLimiterConfig> Config;
            TArray<FPerConnectionInfo> PerConnectionInfos;    // 每连接的饥饿追踪
            UE::Net::FNetBitArray InternalObjectIndices;      // 所有对象的位数组
            FRoundRobinState RoundRobinState;
            FFillState FillState;
            uint32 PrioFrame;                                 // 当前帧号(每帧递增)
        
            enum : unsigned { ObjectGrowCount = 64U };        // 每次增长 64 个对象
        };

        🔄 RoundRobin 模式(轮询)

        "排好队,一个一个来!" 🔄

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔄 RoundRobin 轮询原理                             │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  对象池:[🪙0] [🪙1] [🪙2] [🪙3] [🪙4] [🪙5] [🪙6] [🪙7] [🪙8] [🪙9]│
        │                                                                │
        │  MaxObjectCount = 3                                            │
        │                                                                │
        │  帧1: NextIndex=0 → 选择 [🪙0] [🪙1] [🪙2] → NextIndex=3       │
        │  帧2: NextIndex=3 → 选择 [🪙3] [🪙4] [🪙5] → NextIndex=6       │
        │  帧3: NextIndex=6 → 选择 [🪙6] [🪙7] [🪙8] → NextIndex=9       │
        │  帧4: NextIndex=9 → 选择 [🪙9] [🪙0] [🪙1] → NextIndex=2       │
        │                      ↑ 回绕到开头                              │
        │                                                                │
        │  特点:                                                        │
        │  • 简单高效,O(N) 复杂度                                       │
        │  • 公平轮询,每个对象都有机会                                  │
        │  • 内存开销低(只需一个位数组)                                │
        │  • 所有连接共享同一个轮询状态                                  │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        完整的 PrePrioritize 实现:

        CPP
        // 📍 源文件:NetObjectCountLimiter.cpp
        
        void UNetObjectCountLimiter::PrePrioritize(FNetObjectPrePrioritizationParams& Params){
            // 每帧递增帧号(用于 Fill 模式的饥饿计算)
            ++PrioFrame;
            
            switch (Config->Mode)
            {
                case ENetObjectCountLimiterMode::RoundRobin:
                {
                    PrePrioritizeForRoundRobin();
                    break;
                };
        
                case ENetObjectCountLimiterMode::Fill:
                {
                    // Fill 模式不需要 PrePrioritize,因为每个连接独立处理
                    break;
                };
            };
        }
        
        void UNetObjectCountLimiter::PrePrioritizeForRoundRobin(){
            IRIS_PROFILER_SCOPE(UNetObjectCountLimiter_PrePrioritizeForRoundRobin);
        
            // 查找下 N 个可用对象。这些对象将被所有连接考虑复制(如果有脏数据)
            const uint32 IndexCount = InternalObjectIndices.GetNumBits();
            
            // 重置本帧要考虑的对象位数组
            RoundRobinState.InternalObjectIndices.Init(IndexCount);
            
            // 🔄 处理回绕:如果索引超出范围,从头开始
            if (RoundRobinState.NextIndexToConsider >= IndexCount)
            {
                RoundRobinState.NextIndexToConsider = 0;
            }
        
            const uint32 MaxObjectCount = Config->MaxObjectCount;
            // 遵循用户配置。如果 MaxObjectCount=0,不会优先级化任何对象
            // 对象只会在构造时复制一次,之后不再复制
            if (MaxObjectCount == 0)
            {
                return;
            }
        
            // 使用栈分配临时数组(避免堆分配)
            uint32* Indices = static_cast<uint32*>(FMemory_Alloca(MaxObjectCount * sizeof(uint32)));
            
            // 从当前位置获取 N 个已设置的位
            uint32 ObjectCount = InternalObjectIndices.GetSetBitIndices(
                RoundRobinState.NextIndexToConsider, ~0U, Indices, MaxObjectCount);
            
            // 如果没找够且不是从头开始,从头继续找
            if (RoundRobinState.NextIndexToConsider > 0 && ObjectCount < MaxObjectCount)
            {
                ObjectCount += InternalObjectIndices.GetSetBitIndices(
                    0U, 
                    RoundRobinState.NextIndexToConsider - 1U, 
                    Indices + ObjectCount, 
                    MaxObjectCount - ObjectCount);
            }
        
            if (ObjectCount)
            {
                // 更新下次开始位置(最后一个选中对象的下一个位置)
                RoundRobinState.NextIndexToConsider = static_cast<uint16>(Indices[ObjectCount - 1U] + 1U);
                
                // 在位数组中标记本帧要考虑的对象
                for (uint32 Index : MakeArrayView(Indices, ObjectCount))
                {
                    RoundRobinState.InternalObjectIndices.SetBit(Index);
                }
            }
        }

        完整的 PrioritizeForRoundRobin 实现:

        CPP
        // 📍 源文件:NetObjectCountLimiter.cpp
        
        void UNetObjectCountLimiter::PrioritizeForRoundRobin(FNetObjectPrioritizationParams& Params) const{
            IRIS_PROFILER_SCOPE(UNetObjectCountLimiter_PrioritizeForRoundRobin);
        
            const bool bEnableOwnedObjectsFastLane = Config->bEnableOwnedObjectsFastLane;
            const float StandardPriority = Config->Priority;
            const float OwningConnectionPriority = Config->OwningConnectionPriority;
            const uint32 MaxConsiderCount = Config->MaxObjectCount;
            
            uint32 ConsiderCount = 0U;
            
            if (bEnableOwnedObjectsFastLane)
            {
                // ═══════════════════════════════════════════════════════════════
                // 🚀 快速通道模式:拥有的对象不计入 MaxObjectCount
                // ═══════════════════════════════════════════════════════════════
                for (const uint32 ObjectIndex : MakeArrayView(Params.ObjectIndices, Params.ObjectCount))
                {
                    const FObjectInfo& Info = static_cast<const FObjectInfo&>(
                        Params.PrioritizationInfos[ObjectIndex]);
                    
                    // 检查是否在本帧轮询列表中
                    const bool bIsRoundRobin = RoundRobinState.InternalObjectIndices.GetBit(
                        Info.GetPrioritizerInternalIndex());
                    // 检查是否被当前连接拥有
                    const bool bIsOwnedByConnection = Info.GetOwningConnection() == Params.ConnectionId;
                    
                    // 必须是轮询对象或拥有的对象
                    if (!(bIsRoundRobin | bIsOwnedByConnection))
                    {
                        continue;
                    }
        
                    if (bIsOwnedByConnection)
                    {
                        // 🚀 快速通道:拥有的对象总是获得优先级,不计入配额
                        Params.Priorities[ObjectIndex] = OwningConnectionPriority;
                    }
                    else if (ConsiderCount < MaxConsiderCount)
                    {
                        // 普通对象:计入配额
                        ++ConsiderCount;
                        Params.Priorities[ObjectIndex] = StandardPriority;
                    }
                    // else: 已达到配额,跳过此对象
                }
            }
            else
            {
                // ═══════════════════════════════════════════════════════════════
                // 无快速通道:所有对象平等对待
                // ═══════════════════════════════════════════════════════════════
                for (const uint32 ObjectIndex : MakeArrayView(Params.ObjectIndices, Params.ObjectCount))
                {
                    const FObjectInfo& Info = static_cast<const FObjectInfo&>(
                        Params.PrioritizationInfos[ObjectIndex]);
                    
                    // 只处理本帧轮询列表中的对象
                    if (!RoundRobinState.InternalObjectIndices.GetBit(Info.GetPrioritizerInternalIndex()))
                    {
                        continue;
                    }
        
                    // 拥有的对象获得更高优先级,但仍计入配额
                    const float Priority = (Info.GetOwningConnection() == Params.ConnectionId 
                        ? OwningConnectionPriority 
                        : StandardPriority);
                    Params.Priorities[ObjectIndex] = Priority;
                    
                    // 达到配额后停止
                    if (++ConsiderCount == MaxConsiderCount)
                    {
                        break;
                    }
                }
            }
        }

        📥 Fill 模式(填充 + 饥饿预防)

        "最久没吃饭的先吃!" 🍽️

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📥 Fill 模式饥饿预防机制                           │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  核心思想:记录每个对象"上次被考虑"的帧号                       │
        │                                                                │
        │  对象    上次考虑帧    当前帧=100    饥饿度                     │
        │  ─────────────────────────────────────────────────────────────│
        │  🪙A      帧 98         100-98=2     低(刚吃过)              │
        │  🪙B      帧 50         100-50=50    高(饿了50帧!)          │
        │  🪙C      帧 95         100-95=5     中等                      │
        │  🪙D      帧 30         100-30=70    很高(饿了70帧!)        │
        │                                                                │
        │  排序后(饥饿度高的优先):                                     │
        │  [🪙D:70] [🪙B:50] [🪙C:5] [🪙A:2]                              │
        │                                                                │
        │  MaxObjectCount=2 → 选择 🪙D 和 🪙B                            │
        │                                                                │
        │  📌 效果:没有对象会被永久"饿死"!                             │
        │                                                                │
        │  📌 注意:帧号回绕也能正常工作!                               │
        │  uint32 溢出后,差值计算仍然正确(无符号算术)                 │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        完整的 PrioritizeForFill 实现:

        CPP
        // 📍 源文件:NetObjectCountLimiter.cpp
        
        void UNetObjectCountLimiter::PrioritizeForFill(FNetObjectPrioritizationParams& Params){
            IRIS_PROFILER_SCOPE(UNetObjectCountLimiter_PrioritizeForFill);
        
            /*
             * ⚠️ 警告:如果同一帧同一连接多次进入此函数,说明对象数量太多被分批处理了。
             * Fill 模式要求所有脏对象在同一批次中,以便正确判断哪些对象最久未被复制。
             * 如果触发此警告,需要增加优先级系统的批大小,或改用 RoundRobin 模式。
             * 当此警告发生时,每个批次可能都会优先级化 N 个对象。
             */
            ensureMsgf(FillState.LastPrioFrame != PrioFrame || FillState.LastConnectionId != Params.ConnectionId, 
                TEXT("UNetObjectCountLimiter::PrioritizeForFill. Too many objects are being prioritized"));
        
            FillState.LastPrioFrame = PrioFrame;
            FillState.LastConnectionId = Params.ConnectionId;
        
            const uint32 ConnectionId = Params.ConnectionId;
            FPerConnectionInfo& ConnectionInfo = PerConnectionInfos[ConnectionId];
        
            // 确保有足够空间存储所有对象的帧计数
            if (static_cast<uint32>(ConnectionInfo.LastConsiderFrames.Num()) < InternalObjectIndices.GetNumBits())
            {
                /*
                 * 新对象的 LastConsiderFrame 初始化为 0 是可以的。
                 * 新对象无论如何都会被复制用于创建,如果它们被此优先级考虑,
                 * 我们不会浪费带宽添加更多对象。
                 */
                ConnectionInfo.LastConsiderFrames.SetNum(InternalObjectIndices.GetNumBits());
            }
        
            uint32* LastConsideredFrames = ConnectionInfo.LastConsiderFrames.GetData();
        
            // ═══════════════════════════════════════════════════════════════
            // 📊 排序信息结构
            // ═══════════════════════════════════════════════════════════════
            struct FSortInfo
            {
                uint32 ObjectIndex;               // 全局对象索引
                uint32 InternalIndex;             // 优先级内部索引
                uint32 FrameCountSinceConsidered; // 🍽️ 饥饿度:距上次被考虑的帧数
                bool bIsOwnedByConnection;        // 是否被当前连接拥有
            };
        
            // 使用栈分配(最多约 16KB,可接受)
            FSortInfo* SortInfosAlloc = static_cast<FSortInfo*>(
                FMemory_Alloca(sizeof(FSortInfo) * Params.ObjectCount));
            TArrayView<FSortInfo> SortInfos = MakeArrayView(SortInfosAlloc, Params.ObjectCount);
        
            // ═══════════════════════════════════════════════════════════════
            // 准备排序数据
            // ═══════════════════════════════════════════════════════════════
            {
                FSortInfo* SortInfoIter = SortInfosAlloc;
                for (const uint32 ObjectIndex : MakeArrayView(Params.ObjectIndices, Params.ObjectCount))
                {
                    const FObjectInfo& ObjectInfo = static_cast<const FObjectInfo&>(
                        Params.PrioritizationInfos[ObjectIndex]);
                    const uint32 PrioritizerInternalIndex = ObjectInfo.GetPrioritizerInternalIndex();
        
                    FSortInfo& SortInfo = *SortInfoIter++;
                    SortInfo.ObjectIndex = ObjectIndex;
                    SortInfo.InternalIndex = PrioritizerInternalIndex;
                    // 🔑 饥饿度计算:当前帧 - 上次被考虑的帧
                    // 即使帧号溢出回绕,无符号减法仍然正确
                    SortInfo.FrameCountSinceConsidered = PrioFrame - LastConsideredFrames[PrioritizerInternalIndex];
                    SortInfo.bIsOwnedByConnection = (ObjectInfo.GetOwningConnection() == ConnectionId);
                }
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 排序并设置优先级
            // ═══════════════════════════════════════════════════════════════
            {
                const float StandardPriority = Config->Priority;
                const float OwningConnectionPriority = Config->OwningConnectionPriority;
                const uint32 MaxConsiderCount = Config->MaxObjectCount;
                uint32 ConsiderCount = 0U;
        
                if (Config->bEnableOwnedObjectsFastLane)
                {
                    // 🚀 快速通道模式:拥有的对象优先,然后按饥饿度排序
                    auto ByOwnerAndLeastConsidered = [](const FSortInfo& A, const FSortInfo& B)
                    {
                        // 1. 拥有的对象优先
                        if (A.bIsOwnedByConnection != B.bIsOwnedByConnection)
                        {
                            return A.bIsOwnedByConnection;
                        }
                        // 2. 饥饿度高的优先(更久没被考虑)
                        if (A.FrameCountSinceConsidered != B.FrameCountSinceConsidered)
                        {
                            return A.FrameCountSinceConsidered > B.FrameCountSinceConsidered;
                        }
                        // 3. 平局打破器:使用内部索引保证稳定排序
                        return (A.InternalIndex < B.InternalIndex);
                    };
        
                    Algo::Sort(SortInfos, ByOwnerAndLeastConsidered);
        
                    for (const FSortInfo& Info : SortInfos)
                    {
                        // 更新最后考虑帧(重置饥饿计数器)
                        LastConsideredFrames[Info.InternalIndex] = PrioFrame;
        
                        const float Priority = (Info.bIsOwnedByConnection 
                            ? OwningConnectionPriority 
                            : StandardPriority);
                        Params.Priorities[Info.ObjectIndex] = Priority;
                        
                        // 排序保证拥有的对象在前面,所以达到配额时可以直接停止
                        // 拥有的对象不计入配额
                        ConsiderCount += !Info.bIsOwnedByConnection;
                        if (ConsiderCount == MaxConsiderCount)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    // 无快速通道:只按饥饿度排序
                    auto ByLeastConsidered = [](const FSortInfo& A, const FSortInfo& B)
                    {
                        if (A.FrameCountSinceConsidered != B.FrameCountSinceConsidered)
                        {
                            return A.FrameCountSinceConsidered > B.FrameCountSinceConsidered;
                        }
                        return (A.InternalIndex < B.InternalIndex);
                    };
                    Algo::Sort(SortInfos, ByLeastConsidered);
        
                    for (const FSortInfo& Info : SortInfos)
                    {
                        LastConsideredFrames[Info.InternalIndex] = PrioFrame;
        
                        const float Priority = (Info.bIsOwnedByConnection 
                            ? OwningConnectionPriority 
                            : StandardPriority);
                        Params.Priorities[Info.ObjectIndex] = Priority;
                        
                        if (++ConsiderCount == MaxConsiderCount)
                        {
                            break;
                        }
                    }
                }
            }
        }

        📊 两种模式对比

        特性

        RoundRobin

        Fill

        对象选择

        固定轮询顺序

        最久未复制的优先

        饥饿预防

        隐式(轮询保证)

        显式(帧计数排序)

        内存开销

        低(位数组)

        高(每连接每对象帧计数)

        计算复杂度

        O(N)

        O(N log N)(排序)

        适用场景

        对象修改频率均匀

        对象修改频率差异大

        公平性

        绝对公平

        按需公平(饿的先吃)

        🚀 快速通道(OwnedObjectsFastLane)

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🚀 快速通道机制                                    │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  bEnableOwnedObjectsFastLane = true 时:                       │
        │                                                                │
        │  玩家A的连接:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  🚀 快速通道(不受 MaxObjectCount 限制)                  │  │
        │  │  • 玩家A拥有的宠物🐕                                     │  │
        │  │  • 玩家A拥有的武器🗡️                                     │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  ┌─────────────────────────────────────────────────────────┐  │
        │  │  📦 普通通道(受 MaxObjectCount 限制)                    │  │
        │  │  • 其他玩家的对象                                        │  │
        │  │  • 世界中的金币🪙                                        │  │
        │  └─────────────────────────────────────────────────────────┘  │
        │                                                                │
        │  效果:自己的东西永远不会被"饿死"!                            │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        👁️ 6.4 ReplicationView(复制视图)

        ReplicationView 是优先级系统的"眼睛"👁️,它告诉优先级:玩家在哪里、看向哪里、视野多大。

        📋 视图定义

        CPP
        // 📍 源文件:Core/Public/Iris/ReplicationSystem/ReplicationView.h
        
        struct FReplicationView
        {
            struct FView
            {
                FNetHandle Controller;    // 🎮 控制器(通常是 PlayerController)
                FNetHandle ViewTarget;    // 🎯 视图目标(通常是 Pawn)
                FVector Pos = FVector::ZeroVector;   // 👁️ 观察者位置
                FVector Dir = FVector::ForwardVector; // 🎯 观察方向
                float FoVRadians = UE_HALF_PI;       // 📐 视野角度(弧度,默认90°)
            };
            
            // 支持分屏,默认内联4个视图
            TArray<FView, TInlineAllocator<UE_IRIS_INLINE_VIEWS_PER_CONNECTION>> Views;
        };

        🔄 视图更新流程

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🔄 ReplicationView 更新流程                        │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  游戏代码                                                       │
        │       │                                                        │
        │       ▼                                                        │
        │  PlayerController::GetPlayerViewPoint()                        │
        │       │                                                        │
        │       ▼ 获取位置和方向                                         │
        │  ┌──────────────────────────────────────────────────────────┐ │
        │  │  FReplicationView View;                                   │ │
        │  │  FReplicationView::FView& V = View.Views.AddDefaulted_GetRef();│
        │  │  V.Controller = ControllerHandle;                         │ │
        │  │  V.ViewTarget = PawnHandle;                               │ │
        │  │  V.Pos = ViewLocation;                                    │ │
        │  │  V.Dir = ViewRotation.Vector();                           │ │
        │  │  V.FoVRadians = FMath::DegreesToRadians(FOV);             │ │
        │  └──────────────────────────────────────────────────────────┘ │
        │       │                                                        │
        │       ▼                                                        │
        │  ReplicationSystem->SetReplicationView(ConnectionId, View);    │
        │       │                                                        │
        │       ▼ 存储到连接信息中                                       │
        │  ┌──────────────────────────────────────────────────────────┐ │
        │  │  // ReplicationConnections.cpp                            │ │
        │  │  void SetReplicationView(uint32 ConnectionId,             │ │
        │  │                          const FReplicationView& View)    │ │
        │  │  {                                                        │ │
        │  │      ReplicationViews[ConnectionId] = View;               │ │
        │  │  }                                                        │ │
        │  └──────────────────────────────────────────────────────────┘ │
        │       │                                                        │
        │       ▼ 优先级计算时使用                                       │
        │  Prioritizer->Prioritize(Params);  // Params.View = 存储的视图│
        │                                                                │
        └────────────────────────────────────────────────────────────────┘

        🎮 与 PlayerController 的关联

        CPP
        // 典型的游戏代码中设置视图void AMyGameMode::UpdateReplicationViews(){
            for (APlayerController* PC : GetWorld()->GetPlayerControllerIterator())
            {
                if (!PC->IsLocalController())  // 只为远程玩家设置
                {
                    FReplicationView View;
                    FReplicationView::FView& V = View.Views.AddDefaulted_GetRef();
                    
                    // 🎮 设置控制器
                    V.Controller = GetNetHandle(PC);
                    
                    // 🎯 设置视图目标(通常是 Pawn)
                    if (APawn* Pawn = PC->GetPawn())
                    {
                        V.ViewTarget = GetNetHandle(Pawn);
                    }
                    
                    // 👁️ 获取视图位置和方向
                    FVector ViewLocation;
                    FRotator ViewRotation;
                    PC->GetPlayerViewPoint(ViewLocation, ViewRotation);
                    V.Pos = ViewLocation;
                    V.Dir = ViewRotation.Vector();
                    
                    // 📐 设置视野角度
                    if (APlayerCameraManager* CamMgr = PC->PlayerCameraManager)
                    {
                        V.FoVRadians = FMath::DegreesToRadians(CamMgr->GetFOVAngle());
                    }
                    
                    // 📤 提交到复制系统
                    ReplicationSystem->SetReplicationView(PC->GetConnectionId(), View);
                }
            }
        }

        🎮 多视图支持(分屏游戏)

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🎮 分屏游戏多视图                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  ┌─────────────────┬─────────────────┐                        │
        │  │   玩家A视角     │   玩家B视角     │                        │
        │  │   👤A           │   👤B           │                        │
        │  └─────────────────┴─────────────────┘                        │
        │                                                                │
        │  同一个连接,两个视图!                                         │
        │  Views[0]: PlayerA 的位置和方向                                │
        │  Views[1]: PlayerB 的位置和方向                                │
        │                                                                │
        │  优先级计算:取两个视图中的最高优先级                          │
        │  例如:对象在A附近(0.8),离B很远(0.1) → 最终 = max(0.8,0.1)   │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        CPP
        // 分屏游戏的视图设置void SetupSplitScreenViews(uint32 ConnectionId, 
                                   APlayerController* PC1, 
                                   APlayerController* PC2){
            FReplicationView View;
            
            // 第一个玩家的视图
            {
                FReplicationView::FView& V = View.Views.AddDefaulted_GetRef();
                V.Controller = GetNetHandle(PC1);
                V.ViewTarget = GetNetHandle(PC1->GetPawn());
                PC1->GetPlayerViewPoint(V.Pos, V.Dir);
            }
            
            // 第二个玩家的视图
            {
                FReplicationView::FView& V = View.Views.AddDefaulted_GetRef();
                V.Controller = GetNetHandle(PC2);
                V.ViewTarget = GetNetHandle(PC2->GetPawn());
                PC2->GetPlayerViewPoint(V.Pos, V.Dir);
            }
            
            ReplicationSystem->SetReplicationView(ConnectionId, View);
        }

        👑 视图目标特殊处理

        视图目标(Controller 和 ViewTarget)会获得超高优先级,确保玩家自己的角色永远不会延迟同步:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::SetHighPriorityOnViewTargets(
            const TArrayView<float>& Priorities, 
            const FReplicationView& ReplicationView){
            // 收集所有视图目标
            TArray<FNetHandle, TInlineAllocator<16>> ViewTargets;
            for (const FReplicationView::FView& View : ReplicationView.Views)
            {
                if (View.Controller.IsValid())
                {
                    ViewTargets.Add(View.Controller);  // 🎮 控制器
                }
                if (View.ViewTarget != View.Controller && View.ViewTarget.IsValid())
                {
                    ViewTargets.Add(View.ViewTarget);  // 🎯 Pawn
                }
            }
        
            // 为视图目标设置超高优先级
            for (FNetHandle NetHandle : ViewTargets)
            {
                const FInternalNetRefIndex ViewTargetInternalIndex = 
                    NetRefHandleManager->GetInternalIndexFromNetHandle(NetHandle);
                if (ViewTargetInternalIndex != FNetRefHandleManager::InvalidInternalIndex)
                {
                    // 🔥 一千万!绝对最高优先级
                    Priorities[ViewTargetInternalIndex] = ViewTargetHighPriority;  // 1.0E7f
                }
            }
        }

        ⚙️ 6.5 优先级配置

        📝 配置文件示例

        INI
        [/Script/IrisCore.NetObjectPrioritizerDefinitions]; 第一个定义成为默认空间优先级
        +NetObjectPrioritizerDefinitions=(PrioritizerName="SphereNetObjectPrioritizer",ClassName="/Script/IrisCore.SphereNetObjectPrioritizer")
        
        [/Script/IrisCore.SphereNetObjectPrioritizerConfig]InnerRadius=3000.0OuterRadius=15000.0InnerPriority=1.0OuterPriority=0.2OutsidePriority=0.1
        
        [/Script/IrisCore.ObjectReplicationBridgeConfig]; 为特定类指定优先级
        +PrioritizerConfigs=(ClassName="/Script/MyGame.MyPlayerCharacter",PrioritizerName="FieldOfViewNetObjectPrioritizer")
        +PrioritizerConfigs=(ClassName="/Script/MyGame.GoldCoin",PrioritizerName="NetObjectCountLimiter")

        🔧 6.6 优先级系统内部实现

        📊 FReplicationPrioritization 核心数据结构

        CPP
        // 📍 源文件:ReplicationPrioritization.h
        
        class FReplicationPrioritization
        {
        private:
            // 🔢 优先级常量
            static constexpr float DefaultPriority = 1.0f;
            static constexpr float ViewTargetHighPriority = 1.0E7f;  // 视图目标的超高优先级
        
            // 📦 每连接信息
            struct FPerConnectionInfo
            {
                TArray<float> Priorities;           // 每个对象的累积优先级
                uint32 NextObjectIndexToProcess;    // 下一个要处理的对象索引
                uint32 IsValid : 1;                 // 连接是否有效
            };
        
            // 📊 核心数据
            TArray<FNetObjectPrioritizationInfo> NetObjectPrioritizationInfos;  // 对象优先级信息
            TArray<uint8> ObjectIndexToPrioritizer;   // 对象 → 优先级映射
            TArray<FPrioritizerInfo> PrioritizerInfos; // 优先级信息列表
            TArray<FPerConnectionInfo> ConnectionInfos; // 每连接数据
            TArray<float> DefaultPriorities;           // 默认优先级数组
        };

        🧩 TChunkedArrayWithChunkManagement 分块数组

        Iris 使用自定义的分块数组来高效管理批处理数据:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        /**
         * 带块管理的分块数组
         * 大部分逻辑都围绕批处理展开,因此需要访问块
         */template<class InElementType, uint32 BytesPerChunk>
        class TChunkedArrayWithChunkManagement : public ::TChunkedArray<InElementType, BytesPerChunk>
        {
        private:
            using Super = ::TChunkedArray<InElementType, BytesPerChunk>;
        
        public:
            /** 移除第一个块及其所有元素(如果存在) */
            void PopChunkSafe()
            {
                if (Super::NumElements > 0)
                {
                    Super::NumElements -= FPlatformMath::Min(
                        Super::NumElements, static_cast<int32>(Super::NumElementsPerChunk));
        
                    constexpr int32 Index = 0;
                    constexpr int32 Count = 1;
                    Super::Chunks.RemoveAt(Index, Count, EAllowShrinking::No);
                }
            }
        
            /** 在数组末尾构造新元素,返回引用 */
            InElementType& Emplace_GetRef()
            {
                if ((static_cast<uint32>(Super::NumElements) % static_cast<uint32>(Super::NumElementsPerChunk)) == 0U)
                {
                    // 需要新块
                    ++Super::NumElements;
                    typename Super::FChunk* Chunk = new typename Super::FChunk;
                    Super::Chunks.Add(Chunk);
                    return Chunk->Elements[0];
                }
                else
                {
                    return this->operator[](Super::NumElements++);
                }
            }
        
            /** 返回第一个块中的元素数量 */
            int32 GetFirstChunkNum() const
            {
                return FPlatformMath::Min(Super::NumElements, static_cast<int32>(Super::NumElementsPerChunk));
            }
        
            /** 返回第一个块中第一个元素的指针 */
            const InElementType* GetFirstChunkData() const
            {
                if (typename Super::FChunk const** ChunkPtr = Super::Chunks.GetData())
                {
                    return &(*ChunkPtr)->Elements[0];
                }
                return nullptr;
            }
        
            /** 将元素数量设为零但保留分配 */
            void Reset()
            {
                Super::Chunks.Reset(0);
                Super::NumElements = 0;
            }
        };

        分块数组设计优势:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              📦 分块数组设计优势                                │├────────────────────────────────────────────────────────────────┤│                                                                ││  传统数组问题:                                                 ││  • 大量对象时需要连续大内存                                     ││  • 扩容时需要复制所有数据                                       ││  • 内存碎片化严重                                               ││                                                                ││  分块数组优势:                                                 ││  • 每块 1024 个对象(4KB),适合缓存行                         ││  • 扩容只需分配新块,无需复制                                   ││  • 处理完一块可立即释放(PopChunkSafe)                        ││  • 内存使用更可预测                                             ││                                                                ││  批处理流程:                                                   ││  ┌────────┐ ┌────────┐ ┌────────┐                             ││  │ Chunk1 │→│ Chunk2 │→│ Chunk3 │                             ││  │ 1024个 │ │ 1024个 │ │ 512个  │                             ││  └────────┘ └────────┘ └────────┘                             ││       ↓           ↓           ↓                               ││  处理后释放   处理后释放   处理后释放                           ││                                                                │└────────────────────────────────────────────────────────────────┘

        🔄 完整的 Prioritize 方法

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        /**
         * 优先级计算有多种策略,各有不同的性能特点:
         *
         * 策略1:优先级化本帧所有脏对象 + 每连接因丢包需要的对象
         *   - 丢包应该很少,丢失状态的对象列表应该很小
         *   - 适合脏对象数量相对较少的情况
         *   - 可以节省大量优先级数据设置时间
         *
         * 策略2:从每个连接获取需要优先级化的对象列表
         *   - 适合连接多且作用域对象集合差异大的情况
         *   - 例如:空间过滤后玩家相距很远
         *
         * 性能优化思路:
         *   - 世界生成或延迟加入时,可能有大量对象需要复制
         *   - 可以每帧只考虑一定数量的对象(如 200 个)
         *   - 下一帧从上次的位置继续
         *   - 连接会保持对象的最后优先级值直到计算新值
         */void FReplicationPrioritization::Prioritize(
            const FNetBitArrayView& ReplicatingConnections, 
            const FNetBitArrayView& DirtyObjectsThisFrame){
            IRIS_PROFILER_SCOPE(FReplicationPrioritization_Prioritize);
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段1:更新新增/删除对象的优先级
            // ═══════════════════════════════════════════════════════════════
            UpdatePrioritiesForNewAndDeletedObjects();
            
            // ═══════════════════════════════════════════════════════════════
            // 阶段2:通知优先级有脏对象(用于位置更新等)
            // ═══════════════════════════════════════════════════════════════
            NotifyPrioritizersOfDirtyObjects(DirtyObjectsThisFrame);
        
            if (!ReplicatingConnections.IsAnyBitSet()) 
                return;
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段3:PrePrioritize - 给优先级准备的机会
            // 只有当 Prioritize() 可能被调用时才调用
            // ═══════════════════════════════════════════════════════════════
            {
                FNetObjectPrePrioritizationParams PrePrioParams;
                for (FPrioritizerInfo& Info : PrioritizerInfos)
                {
                    if (Info.ObjectCount == 0U) continue;
                    Info.Prioritizer->PrePrioritize(PrePrioParams);
                }
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段4:为每个连接执行优先级计算
            // ═══════════════════════════════════════════════════════════════
            
            // 使用栈分配连接ID数组
            uint32* ConnectionIds = static_cast<uint32*>(
                FMemory_Alloca(ReplicatingConnections.GetNumBits() * 4));
            uint32 ReplicatingConnectionCount = 0;
            ReplicatingConnections.ForAllSetBits([ConnectionIds, &ReplicatingConnectionCount](uint32 Bit) 
            { 
                ConnectionIds[ReplicatingConnectionCount++] = Bit; 
            });
        
            FPrioritizerBatchHelper BatchHelper(PrioritizerInfos.Num());
            
            for (const uint32 ConnId : MakeArrayView(ConnectionIds, ReplicatingConnectionCount))
            {
                // 🔑 如果没有视图,不进行优先级计算
                if (Connections->GetReplicationView(ConnId).Views.Num() <= 0)
                    continue;
        
                FReplicationConnection* Connection = Connections->GetConnection(ConnId);
                FReplicationWriter* ReplicationWriter = Connection->ReplicationWriter;
                
                // 从 ReplicationWriter 获取需要优先级更新的对象
                const FNetBitArray& Objects = ReplicationWriter->GetObjectsRequiringPriorityUpdate();
                if (Objects.GetNumBits() == 0) continue;
        
                // 执行优先级计算
                PrioritizeForConnection(ConnId, BatchHelper, MakeNetBitArrayView(Objects));
                
                // 🔄 将更新后的优先级传回 ReplicationWriter
                // 当前假设优先级是每对象每连接持久存储的
                {
                    FPerConnectionInfo& ConnInfo = ConnectionInfos[ConnId];
                    const float* Priorities = ConnInfo.Priorities.GetData();
                    ReplicationWriter->UpdatePriorities(Priorities);
                }
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 阶段5:PostPrioritize - 给优先级清理的机会
            // 如果 PrePrioritize() 被调用,则调用此方法
            // ═══════════════════════════════════════════════════════════════
            {
                FNetObjectPostPrioritizationParams PostPrioParams;
                for (FPrioritizerInfo& Info : PrioritizerInfos)
                {
                    if (Info.ObjectCount == 0U) continue;
                    Info.Prioritizer->PostPrioritize(PostPrioParams);
                }
            }
        }

        🎯 PrioritizeForConnection 完整实现

        这是优先级计算的核心方法,负责将对象按优先级分组并批量处理:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        /**
         * 批处理辅助类
         * 将对象按优先级分组,支持分块处理以限制内存使用
         */class FReplicationPrioritization::FPrioritizerBatchHelper
        {
        public:
            enum EConstants : unsigned
            {
                MaxObjectCountPerBatch = 1024U,  // 每批最多 1024 个对象
            };
        
            enum EBatchProcessStatus : unsigned
            {
                ProcessFullBatchesAndContinue,  // 有满批次,处理后继续
                ProcessAllBatchesAndStop,       // 处理所有批次并停止
                NothingToProcess,               // 无需处理
            };
        
            struct FPerPrioritizerInfo
            {
                // 使用分块数组,每块 1024 个对象索引
                TChunkedArrayWithChunkManagement<uint32, MaxObjectCountPerBatch*sizeof(uint32)> ObjectIndices;
            };
        
            TArray<FPerPrioritizerInfo, TInlineAllocator<16>> PerPrioritizerInfos;
        
            /**
             * 准备批次:将对象按优先级分组
             * @return 处理状态
             */
            EBatchProcessStatus PrepareBatch(FPerConnectionInfo& ConnInfo, 
                                              const FNetBitArrayView Objects, 
                                              const uint8* PrioritizerIndices, 
                                              const float* InDefaultPriorities)
            {
                float* ConnPriorities = ConnInfo.Priorities.GetData();
                
                uint32 ObjectIndices[MaxObjectCountPerBatch];
                
                // 获取最多 1024 个脏对象
                for (uint32 ObjectCount = 0; 
                     (ObjectCount = Objects.GetSetBitIndices(BatchInfo.CurrentObjectIndex, ~0U, 
                                                             ObjectIndices, MaxObjectCountPerBatch)) > 0; )
                {
                    // 更新下次开始位置
                    if (ObjectCount < MaxObjectCountPerBatch)
                    {
                        BatchInfo.CurrentObjectIndex = Objects.GetNumBits();
                    }
                    else
                    {
                        BatchInfo.CurrentObjectIndex = ObjectIndices[ObjectCount - 1] + 1U;
                    }
        
                    // 将对象按优先级分组
                    for (const uint32 ObjectIndex : MakeArrayView(ObjectIndices, ObjectCount))
                    {
                        const uint8 PrioritizerIndex = PrioritizerIndices[ObjectIndex];
                        if (PrioritizerIndex == InvalidNetObjectPrioritizerIndex)
                        {
                            continue;  // 静态优先级对象,跳过
                        }
        
                        FPerPrioritizerInfo& PerPrioritizerInfo = PerPrioritizerInfos[PrioritizerIndex];
                        PerPrioritizerInfo.ObjectIndices.Emplace_GetRef() = ObjectIndex;
        
                        // 重置优先级为默认值(优先级会覆盖)
                        ConnPriorities[ObjectIndex] = InDefaultPriorities[ObjectIndex];
                    }
        
                    // 判断返回条件
                    if (ObjectCount < MaxObjectCountPerBatch)
                    {
                        return EBatchProcessStatus::ProcessAllBatchesAndStop;
                    }
                    
                    // 检查是否有优先级达到满批次
                    for (const FPerPrioritizerInfo& Info : PerPrioritizerInfos)
                    {
                        if (Info.ObjectIndices.Num() >= MaxObjectCountPerBatch)
                        {
                            return EBatchProcessStatus::ProcessFullBatchesAndContinue;
                        }
                    }
                }
        
                return EBatchProcessStatus::ProcessAllBatchesAndStop;
            }
        };
        
        /**
         * 为单个连接计算所有对象的优先级
         */void FReplicationPrioritization::PrioritizeForConnection(
            uint32 ConnId, 
            FPrioritizerBatchHelper& BatchHelper, 
            const FNetBitArrayView Objects){
            IRIS_PROFILER_SCOPE(FReplicationPrioritization_PrioritizeForConnection);
        
            FPerConnectionInfo& ConnInfo = ConnectionInfos[ConnId];
        
            // 设置优先级计算参数
            FNetObjectPrioritizationParams PrioParameters;
            PrioParameters.Priorities = ConnInfo.Priorities.GetData();
            PrioParameters.PrioritizationInfos = NetObjectPrioritizationInfos.GetData();
            PrioParameters.ConnectionId = ConnId;
            PrioParameters.View = Connections->GetReplicationView(ConnId);
        
            // 初始化批处理辅助器
            BatchHelper.InitForConnection();
            
            while (true)
            {
                // 准备批次:按优先级分组对象
                const auto ProcessStatus = BatchHelper.PrepareBatch(
                    ConnInfo, Objects, ObjectIndexToPrioritizer.GetData(), DefaultPriorities.GetData());
                
                if (ProcessStatus == FPrioritizerBatchHelper::ProcessAllBatchesAndStop)
                {
                    // ═══════════════════════════════════════════════════════════════
                    // 处理所有批次(最后一批或唯一一批)
                    // ═══════════════════════════════════════════════════════════════
                    for (auto& PerPrioritizerInfo : BatchHelper.PerPrioritizerInfos)
                    {
                        // 处理该优先级的所有块
                        for (int32 ObjectCount = PerPrioritizerInfo.ObjectIndices.Num(); 
                             ObjectCount > 0; 
                             ObjectCount = PerPrioritizerInfo.ObjectIndices.Num())
                        {
                            // 设置本批次的对象列表
                            PrioParameters.ObjectIndices = PerPrioritizerInfo.ObjectIndices.GetFirstChunkData();
                            PrioParameters.ObjectCount = PerPrioritizerInfo.ObjectIndices.GetFirstChunkNum();
        
                            // 获取对应的优先级并调用
                            const int32 PrioritizerIndex = static_cast<int32>(
                                &PerPrioritizerInfo - BatchHelper.PerPrioritizerInfos.GetData());
                            UNetObjectPrioritizer* Prioritizer = PrioritizerInfos[PrioritizerIndex].Prioritizer.Get();
                            
                            // 🎯 调用优先级计算优先级
                            Prioritizer->Prioritize(PrioParameters);
        
                            // 移除已处理的块
                            PerPrioritizerInfo.ObjectIndices.PopChunkSafe();
                        }
                    }
                    break;
                }
                else if (ProcessStatus == FPrioritizerBatchHelper::ProcessFullBatchesAndContinue)
                {
                    // ═══════════════════════════════════════════════════════════════
                    // 处理满批次,然后继续
                    // ═══════════════════════════════════════════════════════════════
                    for (auto& PerPrioritizerInfo : BatchHelper.PerPrioritizerInfos)
                    {
                        // 只处理达到满批次的优先级
                        if (PerPrioritizerInfo.ObjectIndices.Num() < FPrioritizerBatchHelper::MaxObjectCountPerBatch)
                        {
                            continue;
                        }
        
                        PrioParameters.ObjectIndices = PerPrioritizerInfo.ObjectIndices.GetFirstChunkData();
                        PrioParameters.ObjectCount = PerPrioritizerInfo.ObjectIndices.GetFirstChunkNum();
        
                        const int32 PrioritizerIndex = static_cast<int32>(
                            &PerPrioritizerInfo - BatchHelper.PerPrioritizerInfos.GetData());
                        UNetObjectPrioritizer* Prioritizer = PrioritizerInfos[PrioritizerIndex].Prioritizer.Get();
                        Prioritizer->Prioritize(PrioParameters);
        
                        PerPrioritizerInfo.ObjectIndices.PopChunkSafe();
                    }
                    continue;
                }
        
                checkf(false, TEXT("Unexpected BatchProcessStatus %u"), ProcessStatus);
                break;
            }
        
            // 可选:为视图目标设置超高优先级
            if (CVar_ForceConnectionViewerPriority > 0)
            {
                SetHighPriorityOnViewTargets(MakeArrayView(ConnInfo.Priorities), PrioParameters.View);
            }
        }

        🔔 NotifyPrioritizersOfDirtyObjects 完整实现

        这个方法负责通知优先级哪些对象在本帧发生了变化,以便更新位置等信息:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        /**
         * 脏对象更新批处理辅助类
         * 负责将脏对象按优先级分组,并准备 InstanceProtocol 数据
         */class FReplicationPrioritization::FUpdateDirtyObjectsBatchHelper
        {
        public:
            enum Constants : uint32
            {
                MaxObjectCountPerBatch = 512U,  // 每批最多 512 个对象
            };
        
            struct FPerPrioritizerInfo
            {
                uint32* ObjectIndices;                        // 对象索引数组
                uint32 ObjectCount;                           // 对象数量
                FReplicationInstanceProtocol const** InstanceProtocols;  // 实例协议数组
            };
        
            FUpdateDirtyObjectsBatchHelper(const FNetRefHandleManager* InNetRefHandleManager, uint32 PrioritizerCount)
            : NetRefHandleManager(InNetRefHandleManager)
            {
                PerPrioritizerInfos.SetNumUninitialized(PrioritizerCount);
                // 预分配存储空间:每个优先级最多 512 个对象
                ObjectIndicesStorage.SetNumUninitialized(PrioritizerCount * MaxObjectCountPerBatch);
                InstanceProtocolsStorage.SetNumUninitialized(PrioritizerCount * MaxObjectCountPerBatch);
        
                // 为每个优先级分配存储区域
                uint32 PrioritizerIndex = 0;
                for (FPerPrioritizerInfo& PerPrioritizerInfo : PerPrioritizerInfos)
                {
                    PerPrioritizerInfo.ObjectIndices = ObjectIndicesStorage.GetData() + 
                        PrioritizerIndex * MaxObjectCountPerBatch;
                    PerPrioritizerInfo.InstanceProtocols = InstanceProtocolsStorage.GetData() + 
                        PrioritizerIndex * MaxObjectCountPerBatch;
                    ++PrioritizerIndex;
                }
            }
        
            void PrepareBatch(const uint32* ObjectIndices, uint32 ObjectCount, const uint8* PrioritizerIndices)
            {
                ResetBatch();
        
                FPerPrioritizerInfo* PerPrioritizerInfosData = PerPrioritizerInfos.GetData();
                for (const uint32 ObjectIndex : MakeArrayView(ObjectIndices, ObjectCount))
                {
                    const uint8 PrioritizerIndex = PrioritizerIndices[ObjectIndex];
                    if (PrioritizerIndex == FReplicationPrioritization_InvalidNetObjectPrioritizerIndex)
                    {
                        continue;  // 静态优先级对象,跳过
                    }
        
                    // 获取对象的 InstanceProtocol
                    if (const FReplicationInstanceProtocol* InstanceProtocol = 
                        NetRefHandleManager->GetReplicatedObjectDataNoCheck(ObjectIndex).InstanceProtocol)
                    {
                        FPerPrioritizerInfo& PerPrioritizerInfo = PerPrioritizerInfosData[PrioritizerIndex];
                        PerPrioritizerInfo.ObjectIndices[PerPrioritizerInfo.ObjectCount] = ObjectIndex;
                        PerPrioritizerInfo.InstanceProtocols[PerPrioritizerInfo.ObjectCount] = InstanceProtocol;
                        ++PerPrioritizerInfo.ObjectCount;
                    }
                }
            }
        
            TArray<FPerPrioritizerInfo, TInlineAllocator<16>> PerPrioritizerInfos;
        
        private:
            void ResetBatch()
            {
                for (FPerPrioritizerInfo& PerPrioritizerInfo : PerPrioritizerInfos)
                {
                    PerPrioritizerInfo.ObjectCount = 0U;
                }
            }
        
            TArray<uint32> ObjectIndicesStorage;
            TArray<const FReplicationInstanceProtocol*> InstanceProtocolsStorage;
            const FNetRefHandleManager* NetRefHandleManager;
        };
        
        /**
         * 通知优先级哪些对象在本帧发生了变化
         * 优先级可以利用这个信息更新对象的位置等数据
         */void FReplicationPrioritization::NotifyPrioritizersOfDirtyObjects(const FNetBitArrayView& DirtyObjectsThisFrame){
            IRIS_PROFILER_SCOPE(FReplicationPrioritization_NotifyPrioritizersOfDirtyObjects);
        
            FUpdateDirtyObjectsBatchHelper BatchHelper(NetRefHandleManager, PrioritizerInfos.Num());
        
            constexpr SIZE_T MaxBatchObjectCount = FUpdateDirtyObjectsBatchHelper::Constants::MaxObjectCountPerBatch;
            uint32 ObjectIndices[MaxBatchObjectCount];
        
            const uint32 BitCount = ~0U;
            // 分批处理所有脏对象
            for (uint32 ObjectCount, StartIndex = 0; 
                 (ObjectCount = DirtyObjectsThisFrame.GetSetBitIndices(StartIndex, BitCount, ObjectIndices, MaxBatchObjectCount)) > 0; )
            {
                BatchNotifyPrioritizersOfDirtyObjects(BatchHelper, ObjectIndices, ObjectCount);
        
                StartIndex = ObjectIndices[ObjectCount - 1] + 1U;
                if ((StartIndex == DirtyObjectsThisFrame.GetNumBits()) | (ObjectCount < MaxBatchObjectCount))
                {
                    break;
                }
            }
        }
        
        void FReplicationPrioritization::BatchNotifyPrioritizersOfDirtyObjects(
            FUpdateDirtyObjectsBatchHelper& BatchHelper, uint32* ObjectIndices, uint32 ObjectCount){
            BatchHelper.PrepareBatch(ObjectIndices, ObjectCount, ObjectIndexToPrioritizer.GetData());
        
            FNetObjectPrioritizerUpdateParams UpdateParameters;
            UpdateParameters.StateBuffers = &NetRefHandleManager->GetReplicatedObjectStateBuffers();
            UpdateParameters.PrioritizationInfos = NetObjectPrioritizationInfos.GetData();
        
            // 调用每个优先级的 UpdateObjects 方法
            for (const FUpdateDirtyObjectsBatchHelper::FPerPrioritizerInfo& PerPrioritizerInfo : BatchHelper.PerPrioritizerInfos)
            {
                if (PerPrioritizerInfo.ObjectCount == 0)
                {
                    continue;
                }
        
                UpdateParameters.ObjectIndices = PerPrioritizerInfo.ObjectIndices;
                UpdateParameters.ObjectCount = PerPrioritizerInfo.ObjectCount;
                UpdateParameters.InstanceProtocols = PerPrioritizerInfo.InstanceProtocols;
        
                const int32 PrioritizerIndex = static_cast<int32>(
                    &PerPrioritizerInfo - BatchHelper.PerPrioritizerInfos.GetData());
                UNetObjectPrioritizer* Prioritizer = PrioritizerInfos[PrioritizerIndex].Prioritizer.Get();
                
                // 🔑 调用优先级更新对象(位置等信息)
                Prioritizer->UpdateObjects(UpdateParameters);
            }
        }

        🆕 UpdatePrioritiesForNewAndDeletedObjects 完整实现

        这个方法处理新增和删除对象的优先级更新:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        /**
         * 处理新增和删除对象的优先级
         * - 新对象:将默认优先级复制到每个连接
         * - 删除对象:重置优先级为默认值,从优先级中移除
         */void FReplicationPrioritization::UpdatePrioritiesForNewAndDeletedObjects(){
            IRIS_PROFILER_SCOPE(FReplicationPrioritization_UpdatePrioritiesForNewAndDeletedObjects);
        
            // 获取上一帧和当前帧的可作用域对象索引
            const FNetBitArrayView PrevScopedIndices = NetRefHandleManager->GetPrevFrameScopableInternalIndices();
            const FNetBitArrayView ScopedIndices = NetRefHandleManager->GetCurrentFrameScopableInternalIndices();
        
            // ═══════════════════════════════════════════════════════════════
            // 处理删除的对象
            // ═══════════════════════════════════════════════════════════════
            auto ForEachRemovedObject = [this](uint32 ObjectIndex)
            {
                uint8& Prioritizer = ObjectIndexToPrioritizer[ObjectIndex];
                if (Prioritizer != FReplicationPrioritization_InvalidNetObjectPrioritizerIndex)
                {
                    // 从优先级中移除对象
                    FPrioritizerInfo& OldPrioritizerInfo = PrioritizerInfos[Prioritizer];
                    --OldPrioritizerInfo.ObjectCount;
                    OldPrioritizerInfo.Prioritizer->RemoveObject(ObjectIndex, NetObjectPrioritizationInfos[ObjectIndex]);
                }
        
                // 重置为无效优先级和默认优先级
                Prioritizer = FReplicationPrioritization_InvalidNetObjectPrioritizerIndex;
                DefaultPriorities[ObjectIndex] = DefaultPriority;
            };
        
            // ═══════════════════════════════════════════════════════════════
            // 收集新增的对象
            // ═══════════════════════════════════════════════════════════════
            TArray<uint32> NewIndices;
            TFunction<void(uint32)> DoNothing = [](uint32 ObjectIndex){};
            TFunction<void(uint32)> AddIndexAndClearFromNewPriority = [&NewIndices, this](uint32 ObjectIndex)
            {
                NewIndices.Add(ObjectIndex);
                // 防止同一索引被添加两次
                this->ObjectsWithNewStaticPriority.ClearBit(ObjectIndex);
            };
        
            TFunction<void(uint32)> ForEachNewObject = (ConnectionCount > 0 ? AddIndexAndClearFromNewPriority : DoNothing);
            if (ConnectionCount > 0)
            {
                NewIndices.Reserve(FMath::Min(1024U, MaxInternalNetRefIndex));
            }
        
            // 🔑 比较两帧的位数组,找出新增和删除的对象
            FNetBitArrayView::ForAllExclusiveBits(ScopedIndices, PrevScopedIndices, ForEachNewObject, ForEachRemovedObject);
        
            // ═══════════════════════════════════════════════════════════════
            // 处理静态优先级变化的对象
            // ═══════════════════════════════════════════════════════════════
            if (HasNewObjectsWithStaticPriority)
            {
                HasNewObjectsWithStaticPriority = 0;
        
                if (ConnectionCount > 0)
                {
                    ObjectsWithNewStaticPriority.ForAllSetBits([this, &NewIndices](uint32 ObjectIndex)
                    { 
                        NewIndices.Add(ObjectIndex); 
                    });
                }
        
                ObjectsWithNewStaticPriority.ClearAllBits();
            }
        
            // ═══════════════════════════════════════════════════════════════
            // 将新对象的优先级复制到每个连接
            // ═══════════════════════════════════════════════════════════════
            if (NewIndices.Num() > 0 && ConnectionCount > 0)
            {
                const TArrayView<uint32> ObjectIndices = MakeArrayView(NewIndices);
                for (FPerConnectionInfo& ConnectionInfo : ConnectionInfos)
                {
                    if (!ConnectionInfo.IsValid)
                    {
                        continue;
                    }
        
                    for (const uint32 ObjectIndex : ObjectIndices)
                    {
                        ConnectionInfo.Priorities[ObjectIndex] = DefaultPriorities[ObjectIndex];
                    }
                }
            }
        }

        🏭 InitPrioritizers 完整实现

        这个方法负责初始化所有配置的优先级:

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::InitPrioritizers(){
            /**
             * $IRIS TODO: 需要确定热修复支持的类型。
             * 不同的方式有不同的权衡,取决于如何设置对象的优先级
             * 以及用户是否缓存优先级句柄。
             */
            
            // 加载优先级定义
            PrioritizerDefinitions = TStrongObjectPtr<UNetObjectPrioritizerDefinitions>(
                NewObject<UNetObjectPrioritizerDefinitions>());
            TArray<FNetObjectPrioritizerDefinition> Definitions;
            PrioritizerDefinitions->GetValidDefinitions(Definitions);
        
            // 🔑 限制:每个对象存储 uint8 索引,最多支持 256 个优先级
            check(Definitions.Num() <= 256);
        
            PrioritizerInfos.Reserve(Definitions.Num());
            for (FNetObjectPrioritizerDefinition& Definition : Definitions)
            {
                // 创建优先级实例
                TStrongObjectPtr<UNetObjectPrioritizer> Prioritizer(
                    NewObject<UNetObjectPrioritizer>(
                        (UObject*)GetTransientPackage(), 
                        Definition.Class, 
                        MakeUniqueObjectName(nullptr, Definition.Class, Definition.PrioritizerName)));
        
                // 准备初始化参数
                FNetObjectPrioritizerInitParams InitParams;
                InitParams.ReplicationSystem = ReplicationSystem;
                InitParams.Config = (Definition.ConfigClass != nullptr 
                    ? NewObject<UNetObjectPrioritizerConfig>((UObject*)GetTransientPackage(), Definition.ConfigClass) 
                    : nullptr);
                InitParams.AbsoluteMaxNetObjectCount = NetRefHandleManager->GetMaxActiveObjectCount();
                InitParams.CurrentMaxInternalIndex = MaxInternalNetRefIndex;
                InitParams.MaxConnectionCount = Connections->GetMaxConnectionCount();
        
                // 初始化优先级
                Prioritizer->Init(InitParams);
        
                // 注册到优先级列表
                FPrioritizerInfo& Info = PrioritizerInfos.Emplace_GetRef();
                Info.Prioritizer = Prioritizer;
                Info.Name = Definition.PrioritizerName;
                Info.ObjectCount = 0;
            }
        
        #if UE_GAME || UE_SERVER
            UE_CLOG(PrioritizerInfos.Num() == 0, LogIris, Warning, TEXT("%s"), 
                TEXT("No prioritizers have been registered. This may result in a bad gameplay experience "
                     "because nearby actors will not have higher priority than actors far away."));
        #endif
        }

        批处理流程图解:

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐│              🔄 PrioritizeForConnection 批处理流程              │├────────────────────────────────────────────────────────────────┤│                                                                ││  输入:3000 个需要优先级更新的对象                              ││       │                                                        ││       ▼                                                        ││  ┌──────────────────────────────────────────────────────────┐ ││  │  PrepareBatch #1                                         │ ││  │  • 获取前 1024 个对象                                    │ ││  │  • 按优先级分组:                                      │ ││  │    - Sphere: 800 个                                      │ ││  │    - FOV: 200 个                                         │ ││  │    - CountLimiter: 24 个                                 │ ││  │  • 检查:没有满批次,继续获取                            │ ││  └──────────────────────────────────────────────────────────┘ ││       │                                                        ││       ▼                                                        ││  ┌──────────────────────────────────────────────────────────┐ ││  │  PrepareBatch #2                                         │ ││  │  • 获取接下来 1024 个对象                                │ ││  │  • 累计分组:                                            │ ││  │    - Sphere: 1600 个 ← 超过 1024!                       │ ││  │    - FOV: 400 个                                         │ ││  │    - CountLimiter: 48 个                                 │ ││  │  • 返回 ProcessFullBatchesAndContinue                    │ ││  └──────────────────────────────────────────────────────────┘ ││       │                                                        ││       ▼                                                        ││  ┌──────────────────────────────────────────────────────────┐ ││  │  处理满批次                                              │ ││  │  • Sphere->Prioritize(1024 个对象)                       │ ││  │  • 移除已处理的块                                        │ ││  └──────────────────────────────────────────────────────────┘ ││       │                                                        ││       ▼                                                        ││  ┌──────────────────────────────────────────────────────────┐ ││  │  PrepareBatch #3                                         │ ││  │  • 获取剩余 952 个对象                                   │ ││  │  • 返回 ProcessAllBatchesAndStop                         │ ││  └──────────────────────────────────────────────────────────┘ ││       │                                                        ││       ▼                                                        ││  ┌──────────────────────────────────────────────────────────┐ ││  │  处理所有剩余批次                                        │ ││  │  • Sphere->Prioritize(剩余对象)                          │ ││  │  • FOV->Prioritize(所有对象)                             │ ││  │  • CountLimiter->Prioritize(所有对象)                    │ ││  └──────────────────────────────────────────────────────────┘ ││       │                                                        ││       ▼                                                        ││  SetHighPriorityOnViewTargets()  // 视图目标超高优先级        ││                                                                │└────────────────────────────────────────────────────────────────┘

        🔌 连接管理方法

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::AddConnection(uint32 ConnectionId){
            // 确保数组足够大
            if (ConnectionId >= (uint32)ConnectionInfos.Num())
            {
                ConnectionInfos.SetNum(ConnectionId + 1U, EAllowShrinking::No);
            }
        
            ++ConnectionCount;
            FPerConnectionInfo& ConnectionInfo = ConnectionInfos[ConnectionId];
            
            // 🔑 新连接继承默认优先级数组
            ConnectionInfo.Priorities = DefaultPriorities;
            ConnectionInfo.NextObjectIndexToProcess = 0;
            ConnectionInfo.IsValid = 1;
        
            // 通知所有优先级有新连接
            for (FPrioritizerInfo& Info : PrioritizerInfos)
            {
                Info.Prioritizer->AddConnection(ConnectionId);
            }
        }
        
        void FReplicationPrioritization::RemoveConnection(uint32 ConnectionId){
            checkSlow(ConnectionId < (uint32)ConnectionInfos.Num());
        
            --ConnectionCount;
            FPerConnectionInfo& ConnectionInfo = ConnectionInfos[ConnectionId];
            ConnectionInfo.IsValid = 0;
            ConnectionInfo.Priorities.Empty();  // 释放内存
        
            // 通知所有优先级连接已移除
            for (FPrioritizerInfo& Info : PrioritizerInfos)
            {
                Info.Prioritizer->RemoveConnection(ConnectionId);
            }
        }

        📊 SetStaticPriority 和 SetPrioritizer

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::SetStaticPriority(uint32 ObjectIndex, float NewPrio){
            if (!ensureMsgf(NewPrio >= 0.0f, TEXT("Trying to set invalid priority %f"), NewPrio))
            {
                return;
            }
        
            uint8& Prioritizer = ObjectIndexToPrioritizer[ObjectIndex];
            float& Prio = DefaultPriorities[ObjectIndex];
        
            bool bPrioritizerDiffers = Prioritizer != FReplicationPrioritization_InvalidNetObjectPrioritizerIndex;
            bool bPrioDiffers = Prio != NewPrio;
            
            if (bPrioritizerDiffers || bPrioDiffers)
            {
                Prio = NewPrio;
                
                if (bPrioritizerDiffers)
                {
                    // 从旧优先级中移除
                    FPrioritizerInfo& PrioritizerInfo = PrioritizerInfos[Prioritizer];
                    --PrioritizerInfo.ObjectCount;
                    PrioritizerInfo.Prioritizer->RemoveObject(ObjectIndex, NetObjectPrioritizationInfos[ObjectIndex]);
                    Prioritizer = FReplicationPrioritization_InvalidNetObjectPrioritizerIndex;
                }
        
                // 标记需要更新到各连接
                ObjectsWithNewStaticPriority.SetBit(ObjectIndex);
                HasNewObjectsWithStaticPriority = 1;
            }
        }
        
        bool FReplicationPrioritization::SetPrioritizer(uint32 ObjectIndex, FNetObjectPrioritizerHandle NewPrioritizer){
            if (!ensureMsgf(NewPrioritizer != InvalidNetObjectPrioritizerHandle, 
                TEXT("Call SetStaticPriority if you want to use a static priority for the object.")))
            {
                return false;
            }
        
            if (PrioritizerInfos.Num() == 0 || 
                !ensureMsgf(NewPrioritizer < FNetObjectPrioritizerHandle(uint32(PrioritizerInfos.Num())), 
                    TEXT("Trying to set invalid prioritizer 0x%08x"), NewPrioritizer))
            {
                return false;
            }
        
            // 不标记为需要复制新优先级到每个连接
            // 保持旧优先级值,无论之前使用哪个优先级
            // 这在节流优先级计算时应该能正常工作
            DefaultPriorities[ObjectIndex] = 0.0f;
        
            // 从旧优先级注销对象
            uint8& Prioritizer = ObjectIndexToPrioritizer[ObjectIndex];
            FNetObjectPrioritizationInfo& NetObjectPrioritizationInfo = NetObjectPrioritizationInfos[ObjectIndex];
            const bool bWasUsingStaticPriority = (Prioritizer == FReplicationPrioritization_InvalidNetObjectPrioritizerIndex);
            
            if (!bWasUsingStaticPriority)
            {
                FPrioritizerInfo& OldPrioritizerInfo = PrioritizerInfos[Prioritizer];
                --OldPrioritizerInfo.ObjectCount;
                OldPrioritizerInfo.Prioritizer->RemoveObject(ObjectIndex, NetObjectPrioritizationInfo);
            }
        
            // 向新优先级注册对象
            {
                const FNetRefHandleManager::FReplicatedObjectData& ObjectData = 
                    NetRefHandleManager->GetReplicatedObjectDataNoCheck(ObjectIndex);
        
                NetObjectPrioritizationInfo = FNetObjectPrioritizationInfo{};
                FNetObjectPrioritizerAddObjectParams AddParams = {
                    NetObjectPrioritizationInfo, 
                    ObjectData.InstanceProtocol, 
                    ObjectData.Protocol, 
                    NetRefHandleManager->GetReplicatedObjectStateBufferNoCheck(ObjectIndex)
                };
                
                FPrioritizerInfo& PrioritizerInfo = PrioritizerInfos[NewPrioritizer];
                if (PrioritizerInfo.Prioritizer->AddObject(ObjectIndex, AddParams))
                {
                    Prioritizer = static_cast<uint8>(NewPrioritizer);
                    ++PrioritizerInfo.ObjectCount;
                    return true;
                }
        
                // 如果设置新优先级失败,默认使用静态优先级
                UE_LOG(LogIris, Verbose, TEXT("Prioritizer '%s' does not support prioritizing object %u"), 
                    ToCStr(PrioritizerInfo.Prioritizer->GetFName().GetPlainNameString()), ObjectIndex);
        
                // 如果之前使用静态优先级,无需做任何事;否则强制设置默认优先级
                if (!bWasUsingStaticPriority)
                {
                    DefaultPriorities[ObjectIndex] = DefaultPriority;
                    Prioritizer = FReplicationPrioritization_InvalidNetObjectPrioritizerIndex;
                    ObjectsWithNewStaticPriority.SetBit(ObjectIndex);
                    HasNewObjectsWithStaticPriority = 1;
                }
            }
        
            return false;
        }

        📈 优先级累积机制

        这是防止低优先级对象"饿死"的关键机制!

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              📈 优先级累积原理                                  │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  对象A:每帧计算优先级 = 0.3(远处的树)                        │
        │                                                                │
        │  帧1: 累积优先级 = 0.0 + 0.3 = 0.3  ❌ 未被复制(<1.0)        │
        │  帧2: 累积优先级 = 0.3 + 0.3 = 0.6  ❌ 未被复制                 │
        │  帧3: 累积优先级 = 0.6 + 0.3 = 0.9  ❌ 未被复制                 │
        │  帧4: 累积优先级 = 0.9 + 0.3 = 1.2  ✅ 被复制!(≥1.0)        │
        │        └─→ 复制后重置为 0.0                                    │
        │  帧5: 累积优先级 = 0.0 + 0.3 = 0.3  ❌ 重新开始累积            │
        │                                                                │
        │  📌 效果:                                                     │
        │  • 高优先级对象(如 1.0)每帧都被复制                          │
        │  • 低优先级对象(如 0.3)每 4 帧被复制一次                     │
        │  • 没有对象会被永久"饿死"!                                    │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        CPP
        // 📍 源文件:ReplicationWriter.cpp
        
        void FReplicationWriter::UpdatePriorities(const float* UpdatedPriorities){
            IRIS_PROFILER_SCOPE(FReplicationWriter_UpdatePriorities);
        
            auto UpdatePriority = [&LocalPriorities = SchedulingPriorities, UpdatedPriorities](uint32 Index)
            {
                // 🔑 关键:优先级是累加的,不是替换!
                LocalPriorities[Index] += UpdatedPriorities[Index];
            };
        
            // 只更新有脏变化的对象
            ObjectsWithDirtyChanges.ForAllSetBits(UpdatePriority);
        }
        
        // 当对象被成功复制后,重置其优先级void FReplicationWriter::OnObjectReplicated(uint32 ObjectIndex){
            SchedulingPriorities[ObjectIndex] = 0.0f;  // 重置累积优先级
        }

        👑 视图目标高优先级设置

        CPP
        // 📍 源文件:ReplicationPrioritization.cpp
        
        void FReplicationPrioritization::SetHighPriorityOnViewTargets(
            const TArrayView<float>& Priorities, 
            const FReplicationView& ReplicationView){
            using namespace UE::Net::Private;
        
            // 收集所有视图目标(Controller 和 ViewTarget)
            TArray<FNetHandle, TInlineAllocator<16>> ViewTargets;
            for (const FReplicationView::FView& View : ReplicationView.Views)
            {
                if (View.Controller.IsValid())
                {
                    ViewTargets.Add(View.Controller);
                }
                if (View.ViewTarget != View.Controller && View.ViewTarget.IsValid())
                {
                    ViewTargets.Add(View.ViewTarget);
                }
            }
        
            // 为视图目标设置超高优先级
            for (FNetHandle NetHandle : ViewTargets)
            {
                const FInternalNetRefIndex ViewTargetInternalIndex = 
                    NetRefHandleManager->GetInternalIndexFromNetHandle(NetHandle);
                if (ViewTargetInternalIndex != FNetRefHandleManager::InvalidInternalIndex)
                {
                    Priorities[ViewTargetInternalIndex] = ViewTargetHighPriority;  // 1.0E7f
                }
            }
        }

        🚀 性能优化策略

        优化策略

        说明

        性能提升

        SIMD向量化

        每次处理4个对象,使用 VectorRegister

        3-4倍

        批量处理

        每批最多1024个对象,提高缓存命中

        2-3倍

        视图数量优化

        单视图/双视图/多视图分别优化路径

        10-20%

        位置缓存

        TChunkedArray分块存储,避免重复读取

        20-30%

        预计算

        距离平方代替距离,避免开方运算

        5-10%

        分优先级批处理

        相同优先级的对象一起处理

        15-25%

        CPP
        // SIMD 优化示例(SphereNetObjectPrioritizer.cpp)void USphereNetObjectPrioritizer::Prioritize(FNetObjectPrioritizationParams& Params){
            const FReplicationView::FView& View = Params.View.Views[0];
            const VectorRegister ViewPos = VectorLoadFloat3(&View.Pos);
            
            // 预计算常量
            const VectorRegister InnerRadiusSq = VectorSetFloat1(InnerRadius * InnerRadius);
            const VectorRegister OuterRadiusSq = VectorSetFloat1(OuterRadius * OuterRadius);
            
            // 每次处理 4 个对象(SIMD 优化)
            for (uint32 ObjIt = 0; ObjIt < BatchSize; ObjIt += 4)
            {
                // 加载 4 个对象的位置
                VectorRegister Pos0 = GetLocation(Infos[ObjIt + 0]);
                VectorRegister Pos1 = GetLocation(Infos[ObjIt + 1]);
                VectorRegister Pos2 = GetLocation(Infos[ObjIt + 2]);
                VectorRegister Pos3 = GetLocation(Infos[ObjIt + 3]);
                
                // 计算距离平方(避免开方)
                VectorRegister DistSq0 = VectorDistSquared(Pos0, ViewPos);
                // ... 并行计算4个距离
                
                // 线性插值计算优先级
                // Priority = Lerp(InnerPriority, OuterPriority, (DistSq - InnerSq) / (OuterSq - InnerSq))
                
                // 存储结果
                Params.Priorities[ObjIt + 0] = Priority0;
                Params.Priorities[ObjIt + 1] = Priority1;
                Params.Priorities[ObjIt + 2] = Priority2;
                Params.Priorities[ObjIt + 3] = Priority3;
            }
        }

        🎮 6.7 实际应用案例

        🎯 FPS射击游戏配置

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🎯 FPS 游戏优先级策略                              │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  核心需求:                                                     │
        │  • 准星正对的敌人必须精确同步                                   │
        │  • 视野边缘的对象可以稍微延迟                                   │
        │  • 背后的敌人仍需要同步(脚步声等)                             │
        │                                                                │
        │  推荐配置:FieldOfViewNetObjectPrioritizer                     │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        INI
        [/Script/IrisCore.FieldOfViewNetObjectPrioritizerConfig]; 近身区域 - 360度高优先级InnerSphereRadius=2000.0    ; 20米内最高优先级InnerSpherePriority=1.0
        
        ; 视野锥 - 瞄准方向高优先级ConeFieldOfViewDegrees=30.0 ; 窄视野锥(瞄准状态)ConeLength=30000.0          ; 300米瞄准距离MaxConePriority=1.0
        
        ; 准星中心 - 最高优先级LineOfSightWidth=100.0      ; 准星范围LineOfSightPriority=1.0
        
        ; 外围区域OuterSpherePriority=0.3     ; 视野外但在范围内OutsidePriority=0.1         ; 超远距离

        🌍 开放世界RPG配置

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🌍 开放世界 RPG 优先级策略                         │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  核心需求:                                                     │
        │  • 大量NPC和道具需要同步                                        │
        │  • 远处的对象可以低频更新                                       │
        │  • 玩家附近的交互对象优先                                       │
        │                                                                │
        │  推荐配置:SphereNetObjectPrioritizer + CountLimiter           │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        INI
        [/Script/IrisCore.SphereNetObjectPrioritizerConfig]InnerRadius=5000.0     ; 50米内最高优先级OuterRadius=20000.0    ; 200米外最低优先级InnerPriority=1.0OuterPriority=0.2OutsidePriority=0.05   ; 很远的对象几乎不同步
        
        [/Script/IrisCore.ObjectReplicationBridgeConfig]; 为大量相似对象使用 CountLimiter
        +PrioritizerConfigs=(ClassName="/Script/MyGame.GoldCoin",PrioritizerName="NetObjectCountLimiter")
        +PrioritizerConfigs=(ClassName="/Script/MyGame.TreeActor",PrioritizerName="NetObjectCountLimiter")
        +PrioritizerConfigs=(ClassName="/Script/MyGame.GrassActor",PrioritizerName="NetObjectCountLimiter")
        
        [/Script/IrisCore.NetObjectCountLimiterConfig]Mode=Fill              ; 使用填充模式,防止饥饿MaxObjectCount=20      ; 每帧最多考虑20个bEnableOwnedObjectsFastLane=true

        🏎️ 竞速游戏配置

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🏎️ 竞速游戏优先级策略                              │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  核心需求:                                                     │
        │  • 前方的车辆必须精确同步(避免碰撞)                           │
        │  • 后方的车辆可以稍微延迟                                       │
        │  • 所有车辆都需要同步(排名显示)                               │
        │                                                                │
        │  推荐配置:FieldOfViewNetObjectPrioritizer + OwnerBoost        │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        INI
        [/Script/IrisCore.FieldOfViewNetObjectPrioritizerConfig]InnerSphereRadius=3000.0    ; 30米内高优先级ConeFieldOfViewDegrees=60.0 ; 宽视野锥ConeLength=50000.0          ; 500米前方视野MaxConePriority=1.0OutsidePriority=0.3         ; 后方车辆也要同步
        
        [/Script/IrisCore.SphereWithOwnerBoostNetObjectPrioritizerConfig]OwnerPriorityBoost=3.0      ; 自己的车辆优先级加成

        🎪 大逃杀游戏配置

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────┐
        │              🎪 大逃杀游戏优先级策略                            │
        ├────────────────────────────────────────────────────────────────┤
        │                                                                │
        │  核心需求:                                                     │
        │  • 100人同时游戏,带宽压力大                                    │
        │  • 远处的玩家可以低频更新                                       │
        │  • 近距离战斗必须高频同步                                       │
        │                                                                │
        │  推荐配置:Sphere + 激进的距离衰减                             │
        │                                                                │
        └────────────────────────────────────────────────────────────────┘
        INI
        [/Script/IrisCore.SphereNetObjectPrioritizerConfig]InnerRadius=3000.0     ; 30米内高优先级(近战范围)OuterRadius=15000.0    ; 150米外低优先级InnerPriority=1.0OuterPriority=0.1      ; 激进衰减OutsidePriority=0.02   ; 超远几乎不同步
        
        [/Script/IrisCore.NetObjectCountLimiterConfig]; 限制每帧同步的玩家数量MaxObjectCount=30      ; 每帧最多考虑30个玩家Mode=Fill              ; 防止任何玩家被饿死

        📝 6.8 小结

        📊 知识点总结

        知识点

        要点

        优先级作用

        在带宽有限时决定"先发谁"

        静态vs动态

        静态一次设定;动态每帧计算

        计算时机

        在过滤之后执行,只计算过滤后的对象

        优先级累积

        未被复制的对象优先级会累积,防止"饿死"

        视图目标

        玩家角色获得超高优先级(1.0E7f)

        内置优先级

        LocationBased(基类)、Sphere(距离)、FOV(视野)、CountLimiter(数量限制)

        配置方式

        ini文件配置 + 类级别指定

        与过滤协作

        过滤先筛选,优先级后排序

        🏗️ 优先级继承关系

        PLAINTEXT
        UNetObjectPrioritizer (抽象基类)
            │
            ├── ULocationBasedNetObjectPrioritizer (抽象,位置管理)
            │       │
            │       ├── USphereNetObjectPrioritizer (球形距离)
            │       │       │
            │       │       └── USphereWithOwnerBoostNetObjectPrioritizer (所有者加成)
            │       │
            │       └── UFieldOfViewNetObjectPrioritizer (视野锥)
            │
            └── UNetObjectCountLimiter (数量限制)
                    │
                    ├── RoundRobin 模式(轮询)
                    └── Fill 模式(饥饿预防)

        🎯 选择优先级的建议

        游戏类型

        推荐优先级

        原因

        通用/RPG

        SphereNetObjectPrioritizer

        简单高效,距离衰减直观

        FPS/TPS

        FieldOfViewNetObjectPrioritizer

        准星方向需要高精度

        有所有权的对象

        SphereWithOwnerBoostNetObjectPrioritizer

        自己的东西优先同步

        大量相似对象

        NetObjectCountLimiter

        限制数量,防止带宽爆炸

        竞速游戏

        FOV + OwnerBoost

        前方车辆 + 自己的车

        大逃杀

        Sphere + CountLimiter

        距离衰减 + 数量限制

        🚀 性能优化要点

        优化点

        建议

        选择合适的优先级

        不需要视野优先级就用 Sphere,更快

        合理配置距离参数

        根据游戏地图大小调整 Inner/Outer Radius

        使用 CountLimiter

        大量相似对象(金币、树木)必须限制数量

        启用快速通道

        所有者对象走快速通道,减少延迟

        避免过多视图

        分屏游戏视图数量影响性能

        🔧 内部实现关键点

        实现细节

        说明

        FNetObjectPrioritizationInfo

        8字节固定大小,子类重新解释使用

        ObjectIndexToPrioritizer

        uint8数组,最多支持256个优先级

        TChunkedArrayWithChunkManagement

        分块数组,每块1024对象,处理后可立即释放

        FPrioritizerBatchHelper

        按优先级分组对象,支持分批处理

        FUpdateDirtyObjectsBatchHelper

        通知优先级脏对象,每批512个

        SIMD优化

        VectorRegister 4-way并行,显著提升性能

        内存栈分配

        FMemStack避免堆分配,减少内存碎片

        🔧 调试技巧

        CPP
        // 打印对象优先级UE_LOG(LogIris, Log, TEXT("Object %d Priority: %f"), ObjectIndex, Priority);
        
        // 检查视图是否正确设置const FReplicationView& View = ReplicationSystem->GetReplicationView(ConnectionId);
        UE_LOG(LogIris, Log, TEXT("View Pos: %s, Dir: %s"), *View.Views[0].Pos.ToString(), *View.Views[0].Dir.ToString());
        
        // 检查优先级分配
        FNetObjectPrioritizerHandle Handle = ReplicationSystem->GetPrioritizer(ObjectIndex);
        UE_LOG(LogIris, Log, TEXT("Prioritizer Handle: %d"), Handle.GetIndex());

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

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