碎碎念
花了三天时间简单看了看ue5种委托功能的源码,很有收获,很多地方写的很巧妙。
在看源码之前有几个点需要重点关注,比如内存布局,类型擦除,函数签名转换
委托宏
在ue中使用委托需要使用ue自己定义一系列宏,比如
/** Declares a delegate that can only bind to one native function at a time */
#define DECLARE_DELEGATE( DelegateName ) FUNC_DECLARE_DELEGATE( DelegateName, void )
/** Declares a broadcast delegate that can bind to multiple native functions simultaneously */
#define DECLARE_MULTICAST_DELEGATE( DelegateName ) FUNC_DECLARE_MULTICAST_DELEGATE( DelegateName, void )
/** Declares a broadcast thread-safe delegate that can bind to multiple native functions simultaneously */
#define DECLARE_TS_MULTICAST_DELEGATE( DelegateName ) FUNC_DECLARE_TS_MULTICAST_DELEGATE( DelegateName, void )
以及带多个参数的
#define DECLARE_DELEGATE_OneParam( DelegateName, Param1Type ) FUNC_DECLARE_DELEGATE( DelegateName, void, Param1Type )
#define DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type ) FUNC_DECLARE_DELEGATE( DelegateName, void, Param1Type, Param2Type )
拿DECLARE_DELEGATE来举例,可以发现无论几个参数,原型都是FUNC_DECLARE_DELEGATE这个宏,我来看看这个宏的定义。
#define FUNC_DECLARE_DELEGATE( DelegateName, ReturnType, ... ) \
typedef TDelegate<ReturnType(__VA_ARGS__)> DelegateName;
/** Declares a broadcast delegate that can bind to multiple native functions simultaneously */
#define FUNC_DECLARE_MULTICAST_DELEGATE( MulticastDelegateName, ReturnType, ... ) \
typedef TMulticastDelegate<ReturnType(__VA_ARGS__)> MulticastDelegateName;
/** Declares a broadcast thread-safe delegate that can bind to multiple native functions simultaneously */
#define FUNC_DECLARE_TS_MULTICAST_DELEGATE( MulticastDelegateName, ReturnType, ... ) \
typedef TMulticastDelegate<ReturnType(__VA_ARGS__), FDefaultTSDelegateUserPolicy> MulticastDelegateName;
可以发现本体是一个可变参模板类,单播委托的定义是TDelegate<ReturnType(__VA_ARGS__)>,多播委托的本体是TMulticastDelegate<ReturnType(__VA_ARGS__)>。
单播委托 TDelegate
类基础关系
由于meraid的类名好像不能带符号,我就把模板参数在图中先忽略了
TDelegate和TDelegateBase
template <typename DelegateSignature, typename UserPolicy = FDefaultDelegateUserPolicy>
class TDelegate
{
};
template <typename InRetValType, typename... ParamTypes, typename UserPolicy>
class TDelegate<InRetValType(ParamTypes...), UserPolicy> : public TDelegateBase<UserPolicy>
{
.......
};
TDelegate是一个只有函数方法的类,有着大量我们比较熟悉的函数,比如BindUObject(),ExecuteIfBound()等,其中应用了策略模式的设计模式,使用模板参数UserPolicy,可以指定不同实现方式,但是其实TDelegate只是一个空壳。我们接着看TDelegate的父类
template <typename UserPolicy>
class TDelegateBase : public UserPolicy::FDelegateExtras
{
.....
}
可以发现TDelegateBase继承了模板参数UserPolicy::FDelegateExtras,这就是利用模板参数实现的策略模式,接下来看看UserPolicy的默认模板参数FDefaultDelegateUserPolicy
struct FDefaultDelegateUserPolicy
{
using FDelegateInstanceExtras = IDelegateInstance;
using FDelegateExtras = FDelegateBase;
using FMulticastDelegateExtras = TMulticastDelegateBase<FDefaultDelegateUserPolicy>;
};
稍稍有点绕,不过最后我们发现TDelegateBase是继承于FDelegateBase,下面我们分析FDelegateBase的作用。
FDelegateBase
class FDelegateBase
{
void* Allocate(int32 Size)
{
if (IDelegateInstance* CurrentInstance = GetDelegateInstanceProtected())
{
CurrentInstance->~IDelegateInstance();
}
int32 NewDelegateSize = FMath::DivideAndRoundUp(Size, (int32)sizeof(FAlignedInlineDelegateType));
if (DelegateSize != NewDelegateSize)
{
DelegateAllocator.ResizeAllocation(0, NewDelegateSize, sizeof(FAlignedInlineDelegateType));
DelegateSize = NewDelegateSize;
}
return DelegateAllocator.GetAllocation();
}
private:
FDelegateAllocatorType::ForElementType<FAlignedInlineDelegateType> DelegateAllocator;
int32 DelegateSize;
}
inline void* operator new(size_t Size, FDelegateBase& Base)
{
return Base.Allocate((int32)Size);
}
在我看来FDelegateBase起到一个分配内存的作用,这里巧妙的地方要结合后续才能体现出来,我们先不着急,首先这里重载了一个placement new,所以在placement new一个新的FDelegateBase时,重载的new函数会直接调用传入参数Base的Allocate函数。Allocate函数中的DelegateAllocator.ResizeAllocation会调用FMemory::Realloc重新分配DelegateAllocator内部的一段内存大小。
我们来看DelegateAllocator究竟是什么
template<typename ElementType>
class ForElementType : public ForAnyElementType
{
}
class ForAnyElementType
{
ForAnyElementType()
: Data(nullptr)
{
}
FORCEINLINE void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements, SIZE_T NumBytesPerElement)
{
// Avoid calling FMemory::Realloc( nullptr, 0 ) as ANSI C mandates returning a valid pointer which is not what we want.
if (Data || NumElements)
{
//checkSlow(((uint64)NumElements*(uint64)ElementTypeInfo.GetSize() < (uint64)INT_MAX));
Data = (FScriptContainerElement*)BaseMallocType::Realloc( Data, NumElements*NumBytesPerElement );
}
}
FScriptContainerElement* Data; //空类
}
struct FScriptContainerElement
{
};
可以发现ForElementType继承于ForAnyElementType,ForAnyElementType内部有一个指针,用来再开辟空间。
简单的理解就是在placement new时将FDelegateBase内部一段内存扩大指定的Size。目前我们只要记住placement new的时候FDelegateBase内部指针所对应的内存会变大。接下来我们来看这是怎么被应用的。
TDelegate::BindXXXX
让我们先回到TDelegate::Bind系列函数中,由于各种Bind其实是大同小异的。我们谁便拿一个出来,比如这个BindRaw
//Bind
template <typename UserClass, typename... VarTypes>
inline void BindRaw(UserClass* InUserObject, typename TMemFunPtrType<false, UserClass, RetValType (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
{
*this = CreateRaw(InUserObject, InFunc, Vars...);
}
//Create
template <typename UserClass, typename... VarTypes>
UE_NODISCARD inline static TDelegate<RetValType(ParamTypes...), UserPolicy> CreateRaw(UserClass* InUserObject, typename TMemFunPtrType<false, UserClass, RetValType (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
{
static_assert(!TIsConst<UserClass>::Value, "Attempting to bind a delegate with a const object pointer and non-const member function.");
TDelegate<RetValType(ParamTypes...), UserPolicy> Result;
TBaseRawMethodDelegateInstance<false, UserClass, FuncType, UserPolicy, VarTypes...>::Create(Result, InUserObject, InFunc, Vars...);
return Result;
}
//Create
template <typename UserClass, ESPMode Mode, typename... VarTypes>
UE_NODISCARD inline static TDelegate<RetValType(ParamTypes...), UserPolicy> CreateSP(const TSharedRef<UserClass, Mode>& InUserObjectRef, typename TMemFunPtrType<false, UserClass, RetValType (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
{
static_assert(!TIsConst<UserClass>::Value, "Attempting to bind a delegate with a const object pointer and non-const member function.");
TDelegate<RetValType(ParamTypes...), UserPolicy> Result;
TBaseSPMethodDelegateInstance<false, UserClass, Mode, FuncType, UserPolicy, VarTypes...>::Create(Result, InUserObjectRef, InFunc, Vars...);
return Result;
}
BindRaw是绑定一个原生的C++函数,内部调用的CreateRaw函数,在TDelegate中BindXXXX函数内部调用都是CreateXXX函数,所以我们后续直接看Create即可。
BindRaw中是将Create的对象直接拷贝给自己,而Create中又调用TBaseXXXXXXDelegateInstance类型的Create函数,关于TBaseXXXXXXDelegateInstance可以回顾一下最开始的类图,简单的说就算使得每种需要不同方式处理的函数都有一个相对应的TBaseXXXXXXDelegateInstance类去处理。接着我们继续看看Create函数里的内容,顺带一提,要回忆一下刚刚重载new和FDelegateBase会扩大自己内部指针的内存这个特性,因为接下来就会涉及到他的真正用途。我们来看Create函数吧。
template <typename WrappedRetValType, typename... ParamTypes, typename UserPolicy, typename... VarTypes>
class TBaseStaticDelegateInstance<WrappedRetValType(ParamTypes...), UserPolicy, VarTypes...> : public TCommonDelegateInstanceState<WrappedRetValType(ParamTypes...), UserPolicy, VarTypes...>
{
FORCEINLINE static void Create(DelegateBaseType& Base, FFuncPtr InFunc, VarTypes... Vars)
{
using UnwrappedThisType = TBaseStaticDelegateInstance<RetValType(ParamTypes...), UserPolicy, VarTypes...>;
new (Base) UnwrappedThisType(InFunc, Vars...);
}
}
什么Create函数居然把DelegateBaseType类型placement new成了一个TBaseStaticDelegateInstance。这合理吗?他们也没有父子关系。别惊讶,想想前面说的FDelegateBase在placement new时候将自己内部指针所指的内存扩大这个特性。这里其实是将新的TBaseStaticDelegateInstance放到了FDelegateBase内部那个指针所指的那段地址中。
这里new (Base) UnwrappedThisType(InFunc, Vars…);,首先将调用重载的new函数,调用FDelegateBase::Allocate,将自己内部所存指针的内存大小扩大。然后在放下了一个TBaseStaticDelegateInstance。也就是FScriptContainerElement指针内地址指向TBaseStaticDelegateInstance,内存布局变成了
假设FScriptContainerElement*所存的地址为0x000100
那么FDelegateBase内存布局会是这样
0x000000 FScriptContainerElement* 0X000100
0x000008 int32 0x03
0x00000F //End
0X000100 TBaseStaticDelegateInstance 内部数据
0X000200 //End
最后我们的Delegate就算构造完成了。接着我们看看TDelegate是如何调用执行的。
TDelegate::Excute
TDelegate的执行函数是Excute
/**
* Execute the delegate.
*
* If the function pointer is not valid, an error will occur. Check IsBound() before
* calling this method or use ExecuteIfBound() instead.
*
* @see ExecuteIfBound
*/
FORCEINLINE RetValType Execute(ParamTypes... Params) const
{
//using DelegateInstanceInterfaceType = IBaseDelegateInstance<FuncType, UserPolicy>;
DelegateInstanceInterfaceType* LocalDelegateInstance = GetDelegateInstanceProtected();
// If this assert goes off, Execute() was called before a function was bound to the delegate.
// Consider using ExecuteIfSafe() instead.
checkSlow(LocalDelegateInstance != nullptr);
return LocalDelegateInstance->Execute(Params...);
}
可以发现Execute函数是直接从GetDelegateInstanceProtected()拿到一个IBaseDelegateInstance指针直接执行Execute,其实这里就能推测出GetDelegateInstanceProtected()拿的就FDelegateBase中DelegateAllocator中的那个realoc过的指针,然后转成IBaseDelegateInstance,通过虚函数进行调用,让我们看看究竟是不是这样把。
//
FORCEINLINE DelegateInstanceInterfaceType* GetDelegateInstanceProtected() const
{
//using Super = TDelegateBase<UserPolicy>;
return (DelegateInstanceInterfaceType*)Super::GetDelegateInstanceProtected();
}
FORCEINLINE IDelegateInstance* GetDelegateInstanceProtected() const
{
return DelegateSize ? (IDelegateInstance*)DelegateAllocator.GetAllocation() : nullptr;
}
FORCEINLINE ElementType* GetAllocation() const
{
return (ElementType*)ForAnyElementType::GetAllocation();
}
FORCEINLINE FScriptContainerElement* GetAllocation() const
{
return Data;
}
看吧,推测是对的,上面代码省略了一些,全贴出来实在是太长太长了。现在我们已经把TDelegate搞明白了一半了,剩下一半就是IDelegateInstance为父类的DelegateInstance们了。
IDelegateInstance
重新回顾一下类的关系,接着来看IDelegateInstance
class IDelegateInstance
{
public:
virtual ~IDelegateInstance() = default;
virtual UObject* GetUObject( ) const = 0;
virtual const void* GetObjectForTimerManager() const = 0;
virtual uint64 GetBoundProgramCounterForTimerManager() const = 0;
virtual bool HasSameObject( const void* InUserObject ) const = 0;
virtual bool IsSafeToExecute( ) const = 0;
virtual FDelegateHandle GetHandle() const = 0;
};
可以看到IDelegateInstance其实是接口的定义,定义了各种虚函数接口要求子类实现。
接着看IDelegateInstance的子类IBaseDelegateInstance,其实也是一个只定义了一些接口的类。
template <typename RetType, typename... ArgTypes, typename UserPolicy>
struct IBaseDelegateInstance<RetType(ArgTypes...), UserPolicy> : public UserPolicy::FDelegateInstanceExtras
{
virtual void CreateCopy(typename UserPolicy::FDelegateExtras& Base) = 0;
virtual RetType Execute(ArgTypes...) const = 0;
// NOTE: Currently only delegates with no return value support ExecuteIfSafe()
virtual bool ExecuteIfSafe(ArgTypes...) const = 0;
};
TCommonDelegateInstanceState
template <typename InRetValType, typename... ParamTypes, typename UserPolicy, typename... VarTypes>
class TCommonDelegateInstanceState<InRetValType(ParamTypes...), UserPolicy, VarTypes...> : IBaseDelegateInstance<InRetValType(ParamTypes...), UserPolicy>
{
public:
using RetValType = InRetValType;
public:
explicit TCommonDelegateInstanceState(VarTypes... Vars)
: Payload(Vars...)
, Handle (FDelegateHandle::GenerateNewHandle)
{
}
protected:
// Payload member variables (if any).
TTuple<VarTypes...> Payload;
// The handle of this delegate
FDelegateHandle Handle;
};
到这里终于不是接口啦,成员Payload是存放参数的地方,TTuple的实现方式我没有细看,估计和stl的实现方式差不多。但是TTuple内部提供Invoke调用。FDelegateHandle是个一个枚举类型,但是只要一种,就不用太管了。
接着剩下的功能就全部由子类完成啦,比如TBaseStaticDelegateInstance的Execute
template <typename WrappedRetValType, typename... ParamTypes, typename UserPolicy, typename... VarTypes>
class TBaseStaticDelegateInstance<WrappedRetValType(ParamTypes...), UserPolicy, VarTypes...> : public TCommonDelegateInstanceState<WrappedRetValType(ParamTypes...), UserPolicy, VarTypes...>
{
RetValType Execute(ParamTypes... Params) const final
{
// Call the static function
checkSlow(StaticFuncPtr != nullptr);
return this->Payload.ApplyAfter(StaticFuncPtr, Params...);
}
}
调用payload的ApplyAfter函数执行委托,其实ApplyAfter函数里面是一些Invoker调用。
多播委托
TMulticastDelegate
多播委托原理比较简单,就简单说说吧,其实就是TMulticastDelegate内部有一个FDelegateBase数组的结构。就不上代码了。
TTSMulticastDelegate
TTSMulticastDelegate是自带线程安全的,其实也就算再Bind,Execute时候会上锁。
事件
Event其实就多播委托,但是限制了只有指定类可以访问
#define DECLARE_EVENT( OwningType, EventName ) FUNC_DECLARE_EVENT( OwningType, EventName, void )
#define FUNC_DECLARE_EVENT( OwningType, EventName, ReturnType, ... ) \
class EventName : public TMulticastDelegate<ReturnType(__VA_ARGS__)> \
{
\
friend class OwningType; \
};
动态委托
可以暴漏给蓝图的委托,但是我对蓝图目前还不太熟,摸了