# Unreal Iris(三)Filtering && Prioritization
Unreal Iris 并不支持 ReplicationGraph,取而代之的提供了 Filtering System 和 Prioritization。虽然目前还没有 ReplicationGraph 强大,但是性能方面有了很大程度的提升。
# DirtyNetObjectTracker
Iris 的 Filtering 并不是每帧都需要处理所有对象,而是通过 DirtyTracker 追踪每一帧中变化的对象,然后通过 Filtering 得到这些对象应该出现在哪些 Connection 亦或者从哪些 Connection 中被移除。
# Filtering
Filtering 操作主要发生在 TickFlush 的 PreSendUpdate 阶段。其主要作用就是筛选出哪些对象是需要同步的:

在讲解整个 Filter 流程之前,先来看看其组成内容有哪些。Filter System 的实现核心在 FReplicationFiltering 中,大致可以分为四大块:
- Owner Filter:针对对象的过滤。
 - Connection Filter:只对某些连接可见的过滤。
 - Group Filter:多个 Actor 组成一个 Group,该 Group 只对某些连接可见。
 - Dynamic Filter:灵活多变,用于自定义的过滤规则。
 
# Owner Filter
通过 ObjectInternalIndex 进行访问,其功能是指定某个 Object 对某个 Connection 可见的一对一关系。

bool UReplicationSystem::SetFilter(FNetRefHandle Handle, UE::Net::FNetObjectFilterHandle Filter)  | |
constexpr FNetObjectFilterHandle ToOwnerFilterHandle = FNetObjectFilterHandle(1);  | |
Server->ReplicationSystem->SetFilter(ServerObject->NetRefHandle, ToOwnerFilterHandle)  | 
# Connection Filter
通过 ObjectInternalIndex 进行访问,其功能是指定某个 Object 对某些 Connection 可见的一对多关系。

UsedPerObjectInfoStorage 中的每一位都是一个 ConnectionStateIdx,里面是 0/1 则表示对应的 ConnectionStateIdx 是否被使用中。
ConnectionStateIdx 会指向 PerObjectInfoStorage 的一块连续数据,这些数据中的每一位都代表一个有效 Connection 的可见性。

bool UReplicationSystem::SetConnectionFilter(FNetRefHandle Handle, const TBitArray<>& Connections, UE::Net::ENetFilterStatus ReplicationStatus)  | |
TBitArray<> AllowedConnections;  | |
AllowedConnections.Init(false, Client->ConnectionIdOnServer + 1);  | |
AllowedConnections[Client->ConnectionIdOnServer] = true;  | |
Server->ReplicationSystem->SetConnectionFilter(ServerObject->NetRefHandle, AllowedConnections, ENetFilterStatus::Allow);  | 
# Group Filter
通过 ObjectInternalIndex 进行访问,每个 NetObject 最多可以绑定到 4 个 Group 上。Group 通过 GroupHandle 进行访问,并关联一组 Connection。其功能是指定一组 NetObject 对若干个 Connection。
每个 GroupHandle 对应一个 FNetObjectGroup 和 一个 FPerGroupInfo :
FNetObjectGroup:其中可以包含若干个 NetObject 的 ObjectInternalIndex。可以通过 Group 关联上多个 NetObject。FPerGroupInfo:其中包含一个 ConnectionStateIndex。可以通过 Group 关联上多个 Connection。

FNetObjectGroupHandle GroupHandle = Server->ReplicationSystem->CreateGroup();  | |
Server->ReplicationSystem->AddToGroup(GroupHandle, ServerObject->NetRefHandle);  | |
Server->ReplicationSystem->AddGroupFilter(GroupHandle);  | 
# Dynamic Filter
动态过滤,用户自定义的过滤规则。通过 ObjectInternalIndex 进行访问,多个 NetObject 对应一个 DynamicFilter 规则(UNetObjectFilter)。

目前 Iris 提供了三种过滤规则,可以通过配置文件的方式进行添加:

- UFilterOutNetObjectFilter:一刀切什么都不同步。
 - UNetObjectGridFilter:基于网格的距离同步。
 - UNopNetObjectFilter:一刀切什么都同步。
 
[/Script/IrisCore.NetObjectFilterDefinitions] | |
+NetObjectFilterDefinitions=(FilterName=Spatial, ClassName=/Script/IrisCore.NetObjectGridFilter, ConfigClassName=/Script/IrisCore.NetObjectGridFilterConfig)  | |
+NetObjectFilterDefinitions=(FilterName=NotRouted, ClassName=/Script/IrisCore.FilterOutNetObjectFilter, ConfigClassName=/Script/IrisCore.FilterOutNetObjectFilterConfig)  | 
class UNetObjectGridFilterConfig : public UNetObjectFilterConfig  | |
{ | |
GENERATED_BODY()  | |
public:  | |
	/** How many frames a view position should be considered relevant. To avoid culling issues when player borders cells. */ | |
UPROPERTY(Config)  | |
uint32 ViewPosRelevancyFrameCount = 2;  | |
UPROPERTY(Config)  | |
float CellSizeX = 20000.0f;  | |
UPROPERTY(Config)  | |
float CellSizeY = 20000.0f;  | |
	/** Objects with larger sqrt(NetCullDistanceSqr) will be rejected. */ | |
UPROPERTY(Config)  | |
float MaxCullDistance = 20000.0f;  | |
	/** Objects without a NetCullDistanceSquared property will assume to have this value but squared unless there's a cull distance override. */ | |
UPROPERTY(Config)  | |
float DefaultCullDistance = 15000.0f;  | |
	/** Coordinates will be clamped to MinPos and MaxPos. */ | |
UPROPERTY(Config)  | |
FVector MinPos = {-0.5f*2097152.0f, -0.5f*2097152.0f, -0.5f*2097152.0f};  | |
	/** Coordinates will be clamped to MinPos and MaxPos. */ | |
UPROPERTY(Config)  | |
FVector MaxPos = {+0.5f*2097152.0f, +0.5f*2097152.0f, +0.5f*2097152.0f};  | |
};  | 
还可以单独配置每个类型采用何种 Dyanmic Filter:
[/Script/IrisCore.ObjectReplicationBridgeConfig] | |
; Filters | |
DefaultSpatialFilterName=Spatial  | |
; Clear all filters | |
!FilterConfigs=ClearArray  | |
+FilterConfigs=(ClassName=/Script/Engine.LevelScriptActor, DynamicFilterName=NotRouted) ; Not needed  | |
+FilterConfigs=(ClassName=/Script/Engine.Actor, DynamicFilterName=None))  | |
; Info types aren't supposed to have physical representation | |
+FilterConfigs=(ClassName=/Script/Engine.Info, DynamicFilterName=None)  | |
+FilterConfigs=(ClassName=/Script/Engine.PlayerState, DynamicFilterName=None)  | |
; Pawns can be spatially filtered | |
+FilterConfigs=(ClassName=/Script/Engine.Pawn, DynamicFilterName=Spatial))  | |
+FilterConfigs=(ClassName=/Script/EntityActor.SimObject, DynamicFilterName=None))  | 
# Connection Filter Result
Connection 中有单独的结构用于存储过滤过程中每个步骤的结果 ——FReplicationFiltering::FPerConnectionInfo。

- ConnectionFilteredObjects: 连接对对象的可见性状态。这个数据来源于 
NetRefHandleManager->GetScopableInternalIndices()、 Owner Filter 和 Object Connection Filter 的 并集。 - GroupFilteredOutObjects:Group Filter 规则特意让某些 Group 中的 NetObject 对于 Connection 不可见时,会把该状态汇总更新在这里。
 - ObjectsInScopeBeforeDynamicFiltering 基于前两者 AndNotOp 合并后的结果。既对象本身可见或者因为 ConnectionFilter or Owner Filter 可见但又没被 Group 给排除掉的对象。
 - DynamicFilteredOutObjects:因为 Dynamic Filter 而被排除的对象。
 - ObjectsInScope:最终的过滤结果。 
ObjectsInScopeBeforeDynamicFilteringData & ~DynamicFilteredOutObjectsData的结果。 
通过上诉四轮过滤,最终的结果会保留在每个连接的 ObjectsInScope 中,整个过滤过程便会到此结束。对于外部系统来说,只需要关注 ObjectsInScope 的最终结果即可,并且可以通过前后两帧的差异实现进出视野的业务逻辑。
# Prioritization

当对象的 PriorityValue 为 0 的时候,则表示该对象是不会参与同步。

- NetObjectPrioritizationInfos:记录每个对象计算优先级时,根据计算规则,存储所需的数据。
 - ObjectIndexToPrioritizer:标识对象具体需要何种计算规则。
 - PrioritizerInfos:存储所有种类的优先级计算规则。
 - ConnectionInfos:存储所有连接中,各个对象的优先级数值。
 
整体来看就是对所有对象,应用不同的优先级规则,计算出该 NetObject 在各个连接上的优先级数值。定义对象的优先级策略可以通过以下方式实现:
[/Script/IrisCore.ObjectReplicationBridgeConfig] | |
; PrioritizerConfigs | |
+PrioritizerConfigs=(ClassName=/Script/Engine.PlayerState, PrioritizerName=PlayerState)  | |
[/Script/IrisCore.NetObjectPrioritizerDefinitions] | |
+NetObjectPrioritizerDefinitions=(PrioritizerName=Default, ClassName=/Script/IrisCore.SphereNetObjectPrioritizer, ConfigClassName=/Script/IrisCore.SphereNetObjectPrioritizerConfig)  | |
+NetObjectPrioritizerDefinitions=(PrioritizerName=PlayerState, ClassName=/Script/IrisCore.NetObjectCountLimiter, ConfigClassName=/Script/Engine.PlayerStateCountLimiterConfig)  | 
或者通过接口动态设置:
bool UReplicationSystem::SetPrioritizer(FNetRefHandle Handle, FNetObjectPrioritizerHandle Prioritizer)  | |
ReplicationSystem->SetPrioritizer(RefHandle, DefaultSpatialNetObjectPrioritizerHandle);  | 
Unreal 目前支持两种类别的优先级处理:基于距离,基于同步间隔。如果需要自定义规则可以继承 UNetObjectPrioritizer 实现相关的接口:

亦或者指定某个对象的 StaticPriority:
void UReplicationSystem::SetStaticPriority(FNetRefHandle Handle, float Priority)  | |
ReplicationSystem->SetStaticPriority(ServerDependentObject->NetRefHandle, 0.f);  | 
# 依赖关系在优先级中的体现
实际上很多对象的同步顺序会收到依赖关系的影响:
- 如果 Dependent 关系中 EDependentObjectSchedulingHint 设置的 ScheduleBeforeParent,那么会确保 Child 的优先级比 Parent 高一点,这里一般会加上一个 UE_KINDA_SMALL_NUMBER (1.e-4f)。
 - 如果没有这种强依赖关系的情况下,会让两者的优先级持平,存在多个依赖的情况下会取最大者。
 
# 参考链接
- [UFSH2023] Iris Replication System 初探 | 陈宝康 Epic Games