UE4 Delegate(代理)相关源码分析(一)
UE4 Delegate(代理)相关源码分析
前言
- 这是一篇关于UE4代理的一篇分析文章, 但部分涉及C++代理的实现, 请自行百度, 个人也不是很了解深入
- 这篇文章会使对UE4代理相关的认识更深刻, 然并卵.
核心函数
一堆代理的定义
正文
先说下代理的类型, Engine\Source\Runtime\Core\Public\Delegates\DelegateCombinations.h(即代理宏定义的文件)看起来宏很多, 但实际上只有几种, 其他的是有更多的传入参数
DECLARE_DELEGATE( DelegateName ), 定义一个无参代理
DECLARE_MULTICAST_DELEGATE( DelegateName ) 定义一个无参多播
DECLARE_EVENT( OwningType, EventName ) 定义一个无参事件
DECLARE_DYNAMIC_DELEGATE( DelegateName ) 定义一个动态代理
DECLARE_DYNAMIC_MULTICAST_DELEGATE( DelegateName ) 定义一个动态多播代理
DECLARE_DELEGATE_RetVal( ReturnValueType, DelegateName ) 定义一个代理, 带返回参数
DECLARE_DYNAMIC_DELEGATE_RetVal 定义一个动态代理, 带返回参数
- 凡是可以带返回参数的都不可以多播
- 事件是一种特殊的多播, 虽然几乎用不到
基础内容参考官方文档, 懒得解释
https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/Delegates
DECLARE_EVENT( OwningType, EventName )
这个东西比较特殊吧, 反正暂时没有用到过,虽然看着定义用处不小的样子, 笑.
事件与 组播委托 十分相似。虽然任意类均可绑定事件,但只有声明事件的类可以调用事件 的 Broadcast、IsBound 和 Clear 函数。这意味着事件对象可在公共接口中公开,而无需让外部类访问这些敏感度函数。事件使用情况有:在纯抽象类中包含回调、限制外部类调用 Broadcast、IsBound 和 Clear 函数。
https://api.unrealengine.com/CHN/Programming/UnrealArchitecture/Delegates/Events/index.html
按照官方的说法, 应该只有定义事件的类可以调用Broadcast这些函数, 但实际测试, 并不是. 笑
可以看到, 这里是将OwningType变成了多播代理的友元类, 但注意, 这里类是Public继承
然后往里面跳一层, Broadcast是个public函数, 友元此处没卵用, 笑
不确定是否是版本更新导致的, 或者使用方法不对
官方文档中的版本为4.9,
网上并没有找到相关的使用方法教程
附测试代码,版本4.21, 代码简单, 就不做解释了.
UCLASS()
class GUAO_CPLUSPLUSCODE_API AGUAO_CPlusPlusCodeGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
DECLARE_EVENT(AGUAO_CPlusPlusCodeGameModeBase, FTestDelegate);
FTestDelegate TestDelegate;
FTestDelegate& GetTestDelegate() { return TestDelegate; }
void CallDelegate();
};
void AGUAO_CPlusPlusCodeGameModeBase::BeginPlay()
{
Super::BeginPlay();
TestDelegate.AddLambda([this]() {
UE_LOG(LogTemp, Warning, TEXT("Delegate broad cast"));
});
}
void AGUAO_CPlusPlusCodeGameModeBase::CallDelegate()
{
UE_LOG(LogTemp, Warning, TEXT("This is Game mode"));
TestDelegate.Broadcast();
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
FTimerHandle TimerHandle;
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]() {
if (AGUAO_CPlusPlusCodeGameModeBase* CPlusPlusCodeGameMode = Cast<AGUAO_CPlusPlusCodeGameModeBase>(GetWorld()->GetAuthGameMode()))
{
UE_LOG(LogTemp, Warning, TEXT("This is actor"));
CPlusPlusCodeGameMode->TestDelegate.Broadcast();
UE_LOG(LogTemp, Warning, TEXT("This is actor 2"));
AGUAO_CPlusPlusCodeGameModeBase::FTestDelegate& Delegate = CPlusPlusCodeGameMode->GetTestDelegate();
Delegate.Broadcast();
CPlusPlusCodeGameMode->CallDelegate();
}
}, 5.f, false);
}
执行结果
LogTemp: Warning: This is actor
LogTemp: Warning: Delegate broad cast
LogTemp: Warning: This is actor 2
LogTemp: Warning: Delegate broad cast
LogTemp: Warning: This is Game mode
LogTemp: Warning: Delegate broad cast
单播和多播
单播 TBaseDelegate 继承 FDelegateBase
并通过DelegateAllocator存储一个IDelegateInstance对象, 通过这个对象来实现代理.
这个对象下篇文章在深扒吧. 感觉篇幅有点长了, 字数水够了
多播TMulticastDelegate 继承 TBaseMulticastDelegate 继承 FMulticastDelegateBase
多播的核心大概就是这个数组了, 存储所有用于调用的代理实例
配合添加和删除函数
然后就是调用, 遍历数组并依次调用, 如果代理不可用, 就标志 NeedsCompaction为true并整理整个列表.
在单播实现的基础上, 多播其实已经没什么内容了
单播下篇文章分析
代理和动态代理
动态代理可以序列化,它们的函数可以按名称找到,而且它们比常规代理慢。
https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/Delegates/Dynamic
动态代理和代理的继承树是不一致的, 差别很大, 导致动态代理的局限性更大一些
TBaseDynamicDelegate 继承 TScriptDelegate
同时深扒TScriptDelegate存储的变量, 只有一个UObject指针和函数名称, 并根据ProccessDelegate代理的执行函数, 动态代理的实现严重依赖于UE4 强大的反射系统.
所以, 记住绑定的函数要加上UFUNCTION()宏
同时AddDynamic等宏, 传入函数指针, 会转换成相应的函数名称
并可以调用BindFunction来直接进行绑定.
结语
- 水了水, 字数有点多了, 可能是DECLARE_EVENT这个不在计划内吧
DECLARE_EVENT没有用过, 从文档上看过, 一直以为是特殊的多播, 今天深扒了一下, 好像什么用处都没有的样子.
但UE4 应该不会专门写一个没用的东西, 并花篇幅介绍.
所以测试了一下, 结果, 好像就是没用…
可能会下一个4.9版本的旧版本, 再验证一下, - 写着写着, 感觉写的越来越随意了, 贴张图, 写一两句话…
这几个地方的源码看着, 好像都没啥需要说的, 不清楚往里跳一层,
和其他模块关联性不强, 实现也没用特别多的技巧, 就简简单单的.(或者看不动, 笑) - 单播相关的就下周再分析了, 虽然这个好像是处理的最精彩的地方. 但最近有点忙, 再加上懒, 所以就这样了.