UE5源码小记— Delegate(委托)

碎碎念

花了三天时间简单看了看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的类名好像不能带符号,我就把模板参数在图中先忽略了

FDelegateBase
FDelegateAllocatorType::ForElementType DelegateAllocator;
int32 DelegateSize
TDelegateBase
TDelegate
+BindStatic()
+BindLambda()
+BindUObject()
+ExecuteIfBound()
...()
IDelegateInstance
IBaseDelegateInstance
TCommonDelegateInstanceState
TTuple Payload
FDelegateHandle Handle
TBaseFunctorDelegateInstance
-Type Functor
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool
TBaseRawMethodDelegateInstance
UserClass* UserObject;
FMethodPtr MethodPtr;
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool
TBaseUObjectMethodDelegateInstance
TWeakObjectPtr UserObject;
FMethodPtr MethodPtr;
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool

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
IBaseDelegateInstance
TCommonDelegateInstanceState
TTuple Payload
FDelegateHandle Handle
TBaseFunctorDelegateInstance
-Type Functor
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool
TBaseRawMethodDelegateInstance
UserClass* UserObject;
FMethodPtr MethodPtr;
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool
TBaseUObjectMethodDelegateInstance
TWeakObjectPtr UserObject;
FMethodPtr MethodPtr;
+Create(...) : void
+ExecuteIfSafe(ParamTypes... Params) : bool

重新回顾一下类的关系,接着来看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; \
	};

动态委托

可以暴漏给蓝图的委托,但是我对蓝图目前还不太熟,摸了

猜你喜欢

转载自blog.csdn.net/ninesnow_c/article/details/131452901