本文章只是我个人在学习虚幻引擎过程中的理解,不一定正确,若有说的不对的地方,欢迎指正。
以下引用的源码在Engine\Source\Runtime\Core\Public\Delegates\文件夹下的文件内均可找到
在《UE4 代理(Delegate)源码浅析(1)》中我们浅析了一下静态单播代理的源码,这次我们继续讲讲剩下的代理。
一.静态多播代理
静态多播代理和静态单播代理的代码结构有许多相似的地方,所以不会讲的像单播那样详细。多播的名字上多了个**MULTICAST**字样,静态多播代理就是在内部保存一个单播代理的数组,添加绑定就是把绑定函数加入到数组中,执行的时候再依次调用所有绑定的函数,以此来实现多播的效果。
1.大致框架:
我们以DECLARE_MULTICAST_DELEGATE_OneParam为例,先来看看它的基本实现:
#define DECLARE_MULTICAST_DELEGATE_OneParam( DelegateName, Param1Type ) FUNC_DECLARE_MULTICAST_DELEGATE( DelegateName, void, Param1Type )
和单播代理一样最终封装为宏,传入两个宏参数——代理名(DelegateName),参数类型(Param1Type)。再传给FUNC_DECLARE_MULTICAST_DELEGATE宏,再来看看它的定义:
#define FUNC_DECLARE_MULTICAST_DELEGATE( MulticastDelegateName, ... ) \
typedef TMulticastDelegate<__VA_ARGS__> MulticastDelegateName;
FUNC_DECLARE_MULTICAST_DELEGATE接受代理名(MulticastDelegateName)和可变参数。它用类型重定义关键字,把传入可变参数作为模板参数传给模板类TMulticastDelegate,类型重定义为MulticastDelegateName。到这里源码和单播都差不多,下面正式进入不一样的地方。
先看看TMulticastDelegate模板类的定义:
template <typename... ParamTypes>
class TMulticastDelegate<void, ParamTypes...> : public TBaseMulticastDelegate<void, ParamTypes... >
{
private:
// Prevents erroneous use by other classes.
typedef TBaseMulticastDelegate< void, ParamTypes... > Super;
};
TMulticastDelegate的定义挺简单只是重命名一下父类,再把传入的模板参数传给父类TBaseMulticastDelegate,再来看看TBaseMulticastDelegate的大致源码:
template <typename... ParamTypes>
class TBaseMulticastDelegate<void, ParamTypes...> : public FMulticastDelegateBase<FWeakObjectPtr>
{
typedef FMulticastDelegateBase<FWeakObjectPtr> Super;
public:
typedef TBaseDelegate< void, ParamTypes... > FDelegate;
typedef IBaseDelegateInstance<void(ParamTypes...)> TDelegateInstanceInterface;
public:
FDelegateHandle Add(const FDelegate& InNewDelegate)
{}
//……
//一系列添加代理的函数(AddStatic、AddStatic、AddWeakLambda、AddRaw、AddSP、AddThreadSafeSP、AddUFunction、AddUObject)
//……
public:
//移除代理绑定
bool Remove(FDelegateHandle Handle)
{}
protected:
FDelegateHandle AddDelegateInstance(FDelegate&& InNewDelegate)
{}
public:
//执行代理
void Broadcast(ParamTypes... Params) const
{}
protected:
bool RemoveDelegateInstance(FDelegateHandle Handle)
{}
};
从源码可以看到TBaseMulticastDelegate定义了许多种添加绑定函数的方法,每种添加方法根据自己的情况做些处理,最终都会调用Add来,而Add会调用正在实现添加的AddDelegateInstance方法。
暴露了移除某个绑定函数的接口(Remove),注意只是移除一个指定的绑定函数,具体的移除实现再RemoveDelegateInstance中。另外还有执行代理(Broadcast)。具体的函数实现后面会讲。
现在来看看TBaseMulticastDelegate的父类,也是这条继承链的源头:
class FMulticastDelegateBase
{
public:
//……
void Clear()
{}
//……
inline bool IsBound() const
{}
//……
int32 RemoveAll(const void* InUserObject)
{}
//……
private:
//……
TInvocationList InvocationList;
};
FMulticastDelegateBase实现了我们常用的清除所有绑定函数的方法(Clear)、判断绑定的方法(IsBound)。注意这里的RemoveAll和Clear不同,RemoveAll是从调用列表中清除相关Object的绑定函数。
附上我自己做的关系图:
2.实现细节:
记得前文我们知道代理宏封装的是TMulticastDelegate,但多播代理的主要功能都是TMulticastDelegate的祖先(TBaseMulticastDelegate、FMulticastDelegateBase)实现的。
现在我们就来看看它的一些方法和实现细节,首先是添加绑定函数,以AddUObject为例,源码:
template <typename UserClass, typename... VarTypes>
inline FDelegateHandle AddUObject(UserClass* InUserObject, typename TMemFunPtrType<true, UserClass, void (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
{
return Add(FDelegate::CreateUObject(InUserObject, InFunc, Vars...));
}
可以看出AddUObject是模板函数:
有两个模板参数——绑定函数所在类的类型(UserClass)和传给绑定函数的一系列参数的类型(VarTypes);
有三个函数参数——绑定函数所在类的实例(InUserObject)、绑定函数(InFunc)、传给绑定函数的一系列参数(Vars)。
AddUObject调用真正的添加方法Add。调用FDelegate的CreateUObject,那么FDelegate是什么呢?来看看源码就知道了:
typedef TBaseDelegate<void, ParamTypes...> FDelegate;
看以看到,FDelegate是TBaseDelegate的重定义。很熟悉吧,在上一章介绍单播的时候我们就介绍过TBaseDelegate和它的CreateUObject。这里就简单介绍一下——CreateUObject会根据传入的参数创建的并返回一个TBaseDelegate的实例。忘记的小伙伴可以回去《UE4 代理(Delegate)源码浅析(1)》看看它的实现细节。
我们把视线聚焦到Add上:
FDelegateHandle Add(const FDelegate& InNewDelegate)
{
FDelegateHandle Result;
if (Super::GetDelegateInstanceProtectedHelper(InNewDelegate))
{
Result = AddDelegateInstance(FDelegate(InNewDelegate));
}
return Result;
}
GetDelegateInstanceProtectedHelper是实现于FMulticastDelegateBase类内的方法,可以获取传入的待绑定代理的IDelegateInstance,判断一下它是否存在,若存在调用一下AddDelegateInstance传入代理类实例并添加。
注:IDelegateInstance我们在《UE4 代理(Delegate)源码浅析(1)》也是详细讲过的
关于AddDelegateInstance:
FDelegateHandle AddDelegateInstance(FDelegate&& InNewDelegate)
{
return Super::AddInternal(MoveTemp(InNewDelegate));
}
inline FDelegateHandle AddInternal(FDelegateBase&& NewDelegateBaseRef)
{
//……
FDelegateHandle Result = NewDelegateBaseRef.GetHandle();
InvocationList.Add(MoveTemp(NewDelegateBaseRef));
return Result;
}
AddDelegateInstance调用父类的AddInternal方法,传入代理实例。AddInternal使用代理函数表(InvocationList)的Add方法把代理实例添加到函数表中。
现在来看看执行代理(Broadcast)的核心源码:
void Broadcast(ParamTypes... Params) const
{
//……
Super::LockInvocationList();
{
//……
for (int32 InvocationListIndex = LocalInvocationList.Num() - 1; InvocationListIndex >= 0; --InvocationListIndex)
{
const FDelegate& DelegateBase = (const FDelegate&)LocalInvocationList[InvocationListIndex];
//获取代理函数的IDelegateInstance
IDelegateInstance* DelegateInstanceInterface = Super::GetDelegateInstanceProtectedHelper(DelegateBase);
if (DelegateInstanceInterface == nullptr || !((TDelegateInstanceInterface*)DelegateInstanceInterface)->ExecuteIfSafe(Params...))
{
//……
}
}
}
Super::UnlockInvocationList();
//……
}
注:Super::LockInvocationList();和Super::UnlockInvocationList();是用来保证线程安全的锁
最主要的就是中间那个for循环了,它遍历了前文提到的代理函数表(InvocationList)。获取代理函数的IDelegateInstance,上一章讲过代理函数和代理类都保存在其中,最后调用它的ExecuteIfSafe执行保存的代理函数。
这样就实现了多播代理的广播效果,另外笔者看到源码加线程锁的操作,估计执行操作的不止一个线程,因此广播的执行顺序和添加代理函数的顺序不是一样的。
注:关于IDelegateInstance和它的继承链,在《UE4 代理(Delegate)源码浅析(1)》也详细讲过
至于解除代理绑定(Clear)就很简单了:
void Clear( )
{
for (FDelegateBase& DelegateBaseRef : InvocationList)
{
DelegateBaseRef.Unbind();
}
CompactInvocationList(false);
}
前文说过多播就是保存了一个单播的数组,所以只要遍历一下调用函数表,获得每一个单播,然后调用一下Unbind就可以了。
二.事件
可能有小伙伴会问为什么要在这里讲事件呢?从代理的使用那一章我们知道,事件和多播差不多,但多了只有指定的类可以使用的限制。其实在实现上,事件和多播几乎一模一样,我们趁着刚说完多播的劲儿来说一下事件,很快就好。
以DECLARE_EVENT_OneParam为例来看看它的源码:
#define DECLARE_EVENT_OneParam( OwningType, EventName, Param1Type ) FUNC_DECLARE_EVENT( OwningType, EventName, void, Param1Type )
#define FUNC_DECLARE_EVENT( OwningType, EventName, ... ) \
class EventName : public TBaseMulticastDelegate<__VA_ARGS__> \
{ \
friend class OwningType; \
};
基本实现还是熟悉的宏封装,在EventName中声明了一个友元(OwningType)。看起来事件的诸多功能不能被外部类使用,但其实不是这样的,只不过通过声明友元使指定的类拥有事件的全部访问权限。
事件的底层实现就是多播,事件声明了一个继承自TBaseMulticastDelegate的类EventName。熟悉吧,就是多播封装的类(TMulticastDelegate)的父类,忘记了可以回去上面看看。
三.本章小结:
这章紧接着上一章,浅析了一下多播代理的DECLARE_MULTICAST_DELEGATE_OneParam和事件的DECLARE_EVENT_OneParam,剩下的多播和事件基本差不多,想了解的话可以去源码里看,目录就在开头。本章就到这,谢谢观看 。