PushModel属性同步

image-20220410162250125

优势

PushModel 是对 Replicate 的一个优化。

对于属性的同步,原有的 Replicated 属性,在 Replicate 之前需要通过反射对一个 Actor 的所有属性进行 diff,对比所有的属性是否发生变化,变化的属性才进行同步。

而用上了 PushModel,我们可以主动 MARK_DIRTY,在 Set 时主动将其设置为脏,相当于给该属性上了一个Flag,底层会自动通过这个Flag 来判断是否需要同步,省去了 diff 这个高消耗的操作。

需要注意的是,对于一个 Actor,如果采用了 PushModel,则需要将所有需要同步的属性都挂上这个 PushModel,否则如果存在非PushModel的需要Repilicated 的属性,可能会该功能不生效,退化到原来的情况。

UE5 中还对 PushModel 做了更进一步的优化,添加了一些新的宏以及对更多基础组件的支持。

实践

设置依赖

首先,我们需要在 Build.cs 中添加 NetCore 模块的依赖。

1
PublicDependcyModuleNames.AddRange(new string[] {"NetCore"})

设置属性

我们对于一个需要同步的属性,得先加上 Replicated 标签;有必要的话,需要加上RepilicatedUsing 来设置属性为脏时执行的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
UCLASS()
class ATest : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(Replicated, EditAnywhere, ReplicatedUsing = OnRep_Param)
float Param;

UFUNCITON()
void OnRep_Param();

...
}
1
void ATest::OnRep_Param() {...}

声明同步

首先我们要先将这个 Actor 标记为需要同步,在构造函数中标记 bReplicates = true

接着我们需要实现 GetLifetimeReplicatedProps,在这个接口中添加宏标记同步的 Property。

1
2
3
4
5
6
7
8
9
void ATest::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);

FDoRepLifetimeParams SharedParams;
SharedParams.bIsPushBased = true;

DOREPLIFETIME_WITH_PARAMS_FAST(ATest, Param, SharedParams);
}

FDoRepLifetimeParams 中设置了同步的条件,其中bIsPushBased 表明是否使用 PushModel

其中 DOREPLIFETIME_WITH_PARAMS_FAST 的宏实现:

1
2
3
4
5
6
7
define DOREPLIFETIME_WITH_PARAMS_FAST(c,v,params) \
{ \
static const bool bIsValid_##c_##v = ValidateReplicatedClassInheritance(StaticClass(), c::StaticClass(), TEXT(#v)); \
const TCHAR* DoRepPropertyName_##c_##v(TEXT(#v)); \
const NetworkingPrivate::FRepPropertyDescriptor PropertyDescriptor_##c_##v(DoRepPropertyName_##c_##v, (int32)c::ENetFields_Private::v, 1); \
RegisterReplicatedLifetimeProperty(PropertyDescriptor_##c_##v, OutLifetimeProps, params); \
}

修改属性

最后,我们在修改属性的时候,可以使用 MARK_PROPERTY_DIRTY_FROM_NAME 来将属性设脏。

1
2
3
4
5
void ATest::SetParam(const float NewParam)
{
MARK_PROPERTY_DIRTY_FROM_NAME(ATest, Param, this);
Param = NewParam;
}

现在,Server 只需要检测属性是否被 MARK_DIRTY 就可以知道是否需要同步。

当然,除了 MARK_PROPERTY_DIRTY_FROM_NAME 以外,UE 还实现了一些作用类似的其它的宏:

1
2
3
4
5
6
#define MARK_PROPERTY_DIRTY(Object, Property) 
#define MARK_PROPERTY_DIRTY_STATIC_ARRAY_INDEX(Object, RepIndex, ArrayIndex)
#define MARK_PROPERTY_DIRTY_STATIC_ARRAY(Object, RepIndex, ArrayIndex)
#define MARK_PROPERTY_DIRTY_FROM_NAME(ClassName, PropertyName, Object)
#define MARK_PROPERTY_DIRTY_FROM_NAME_STATIC_ARRAY_INDEX(ClassName, PropertyName, ArrayIndex, Object)
#define MARK_PROPERTY_DIRTY_FROM_NAME_STATIC_ARRAY(ClassName, PropertyName, ArrayIndex, Object)

改进

当然,我们也可以不这么复杂,我们可以自己设置一个新的标签。

对于打上这个标签的属性,让其在 CodeGenerator 中自动生成 SetterGetter,然后通过 SetterGetter 来进行修改与访问。

1
2
3
4
5
6
7
8
9
10
11
12
template<typename _T> \
void SetParam(_T&& NewParam) \
{ \
MARK_PROPERTY_DIRTYFROM_NAME(ATest, Param, this); \
Param = Forward<_T>(NewParam); \
} \
const float& GetParam() const {return Param;} \
float& GetParam_Mutable() \
{ \
MARK_PROPERTY_DIRTYFROM_NAME(ATest, Param, this); \
return Param;
} \

参考

Unreal Engine 4. New network model: PushModel:https://tech-en.netlify.app/articles/en539604/index.html