# 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