当前位置: 首页 > 工具软件 > Nice UE > 使用案例 >

UE4之Delegate:组播

东门晓博
2023-12-01

定义

#define FUNC_DECLARE_MULTICAST_DELEGATE( MulticastDelegateName, ... ) \
	typedef TMulticastDelegate<__VA_ARGS__> MulticastDelegateName;
	
template <typename... ParamTypes>
class TMulticastDelegate<void, ParamTypes...> : public TBaseMulticastDelegate<void, ParamTypes... >
{
private:
	typedef TBaseMulticastDelegate< void, ParamTypes... > Super;
};

多播的功能,基本上基于单播,因为多播只是保存了多个单播而已.
多播实际上是一个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;
	
	template <typename UserClass, typename... VarTypes>
	inline FDelegateHandle AddRaw(UserClass* InUserObject, typename TMemFunPtrType<false, UserClass, void (ParamTypes..., VarTypes...)>::Type InFunc, VarTypes... Vars)
	{
		return Add(FDelegate::CreateRaw(InUserObject, InFunc, Vars...));
	}
	
	FDelegateHandle Add(FDelegate&& InNewDelegate)
	{
		FDelegateHandle Result;
		if (Super::GetDelegateInstanceProtectedHelper(InNewDelegate))
		{
			Result = AddDelegateInstance(MoveTemp(InNewDelegate));
		}
		return Result;
	}
	FDelegateHandle Add(const FDelegate& InNewDelegate)
	{
		FDelegateHandle Result;
		if (Super::GetDelegateInstanceProtectedHelper(InNewDelegate))
		{
			Result = AddDelegateInstance(FDelegate(InNewDelegate));
		}
		return Result;
	}
	FDelegateHandle AddDelegateInstance(FDelegate&& InNewDelegate)
	{
		return Super::AddInternal(MoveTemp(InNewDelegate));
	}
	
	void Broadcast(ParamTypes... Params) const
	{
		bool NeedsCompaction = false;
		Super::LockInvocationList();
		{
			const TInvocationList& LocalInvocationList = Super::GetInvocationList();
			// call bound functions in reverse order, so we ignore any instances that may be added by callees
			for (int32 InvocationListIndex = LocalInvocationList.Num() - 1; InvocationListIndex >= 0; --InvocationListIndex)
			{
				// this down-cast is OK! allows for managing invocation list in the base class without requiring virtual functions
				const FDelegate& DelegateBase = (const FDelegate&)LocalInvocationList[InvocationListIndex];

				IDelegateInstance* DelegateInstanceInterface = Super::GetDelegateInstanceProtectedHelper(DelegateBase);
				if (DelegateInstanceInterface == nullptr || !((TDelegateInstanceInterface*)DelegateInstanceInterface)->ExecuteIfSafe(Params...))
				{
					NeedsCompaction = true;
				}
			}
		}
		Super::UnlockInvocationList();
		if (NeedsCompaction)
		{
			const_cast<TBaseMulticastDelegate*>(this)->CompactInvocationList();
		}
	}
};

可以看到的是, 类似于AddRaw的方法,跟单播里那些方法都一样,多播只不过是调用了单播的方法,生成一个单播的代理对象而已. 最后都会调用到父类 FMulticastDelegateBaseAddInternal方法.
还有最主要的Broadcast方法, 遍历代理数组,如果执行不成功(ExecuteIfSafe失败), 则标记这个代理数组需要压缩(CompactInvocationList).

再看看FMulticastDelegateBase

template<typename ObjectPtrType>
class FMulticastDelegateBase
{
public:
	~FMulticastDelegateBase(){}
	void Clear( )
	{
		for (FDelegateBase& DelegateBaseRef : InvocationList)
		{
			DelegateBaseRef.Unbind();
		}
		CompactInvocationList(false);
	}
	// 检查是否有任何功能绑定到此多播委托.
	inline bool IsBound( ) const
	{
		for (const FDelegateBase& DelegateBaseRef : InvocationList)
		{
			if (DelegateBaseRef.GetDelegateInstanceProtected())
			{
				return true;
			}
		}
		return false;
	}
	inline bool IsBoundToObject( void const* InUserObject ) const
	{
		for (const FDelegateBase& DelegateBaseRef : InvocationList)
		{
			IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
			if ((DelegateInstance != nullptr) && DelegateInstance->HasSameObject(InUserObject))
			{
				return true;
			}
		}
		return false;
	}
	
	void RemoveAll( const void* InUserObject )
	{
		if (InvocationListLockCount > 0)
		{
			bool NeedsCompacted = false;
			for (FDelegateBase& DelegateBaseRef : InvocationList)
			{
				IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
				if ((DelegateInstance != nullptr) && DelegateInstance->HasSameObject(InUserObject))
				{
					// Manually unbind the delegate here so the compaction will find and remove it.
					DelegateBaseRef.Unbind();
					NeedsCompacted = true;
				}
			}
			// can't compact at the moment, but set out threshold to zero so the next add will do it
			if (NeedsCompacted)
			{
				CompactionThreshold = 0;
			}
		}
		else
		{
			// compact us while shuffling in later delegates to fill holes
			for (int32 InvocationListIndex = 0; InvocationListIndex < InvocationList.Num();)
			{
				FDelegateBase& DelegateBaseRef = InvocationList[InvocationListIndex];
				IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
				if (DelegateInstance == nullptr
					|| DelegateInstance->HasSameObject(InUserObject)
					|| DelegateInstance->IsCompactable())
				{
					InvocationList.RemoveAtSwap(InvocationListIndex, 1, false);
				}
				else
				{
					InvocationListIndex++;
				}
			}
			CompactionThreshold = FMath::Max(2, 2 * InvocationList.Num());
			InvocationList.Shrink();
		}
	}

protected:
	inline FMulticastDelegateBase( )
		: CompactionThreshold(2)
		, InvocationListLockCount(0) { }
protected:
	inline FDelegateHandle AddInternal(FDelegateBase&& NewDelegateBaseRef)
	{
		// compact but obey threshold of when this will trigger
		CompactInvocationList(true);
		FDelegateHandle Result = NewDelegateBaseRef.GetHandle();
		InvocationList.Add(MoveTemp(NewDelegateBaseRef));
		return Result;
	}

	void CompactInvocationList(bool CheckThreshold=false)
	{
		if (InvocationListLockCount > 0)
		{
			return;
		}
        //如果检查阈值,则服从但衰减. 这是为了确保即使很少调用的委托也将最终在Add()中压缩
		if (CheckThreshold 	&& --CompactionThreshold > InvocationList.Num())
		{
			return;
		}
		int32 OldNumItems = InvocationList.Num();
		// 查找任何null或可压缩的内容并将其删除
		for (int32 InvocationListIndex = 0; InvocationListIndex < InvocationList.Num();)
		{
			FDelegateBase& DelegateBaseRef = InvocationList[InvocationListIndex];
			IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
			if (DelegateInstance == nullptr	|| DelegateInstance->IsCompactable())
			{
				InvocationList.RemoveAtSwap(InvocationListIndex);
			}
			else
			{
				InvocationListIndex++;
			}
		}
		CompactionThreshold = FMath::Max(2, 2 * InvocationList.Num());
		if (OldNumItems > CompactionThreshold)
		{
			// would be nice to shrink down to threshold, but reserve only grows..?
			InvocationList.Shrink();
		}
	}
	inline const TInvocationList& GetInvocationList( ) const
	{
		return InvocationList;
	}
	inline void LockInvocationList( ) const
	{
		++InvocationListLockCount;
	}
	inline void UnlockInvocationList( ) const
	{
		--InvocationListLockCount;
	}
protected:
	static FORCEINLINE IDelegateInstance* GetDelegateInstanceProtectedHelper(const FDelegateBase& Base)
	{
		return Base.GetDelegateInstanceProtected();
	}
private:
	/** Holds the collection of delegate instances to invoke. */
	TInvocationList InvocationList;
	/** Used to determine when a compaction should happen. */
	int32 CompactionThreshold;
	/** Holds a lock counter for the invocation list. */
	mutable int32 InvocationListLockCount;
};

RemoveAll里,因为存在多线程访问的问题,因此会先根据InvocationListLockCount > 0进行判断是否有别的线程正在访问,否则就移除和参数InUserObject相关的代理.

FMulticastDelegateBase里,有个很重要的函数AddInternal, 就是上文提及到的, 所有TBaseMulticastDelegate的Add方法,最后都会调用到AddInternal

inline FDelegateHandle AddInternal(FDelegateBase&& NewDelegateBaseRef)
{
	CompactInvocationList(true);
	FDelegateHandle Result = NewDelegateBaseRef.GetHandle();
	InvocationList.Add(MoveTemp(NewDelegateBaseRef));
	return Result;
}

首先会调用CompactInvocationList压缩一次代理数组, 这个方法里最主要的判断就是判断代理类的IsCompacktable()

if (DelegateInstance == nullptr	|| DelegateInstance->IsCompactable())
{
	InvocationList.RemoveAtSwap(InvocationListIndex);
}

查看所有的代理实现类里,目前只有TBaseUFunctionDelegateInstance, TBaseUObjectMethodDelegateInstance和TWeakBaseFunctorDelegateInstance重写了基类IsCompactable方法,只看看基类的实现

virtual bool IsCompactable( ) const
{
	return !IsSafeToExecute();
}

可以看到,内部是调用了IsSafeToExecute,IsSafeToExecute在各个子类里,也有不同,但基本都是判断代理类是否合理(Valid).

内部有成员变量TInvocationList InvocationList来保存所有代理类FDelegateBase

typedef TArray<FDelegateBase, FMulticastInvocationListAllocatorType> TInvocationList;

FDelegateBase代理类, 内部保存了具体代理对象的实现,单播里已经说过, 比如我们在Add各种操作的时候,就会在FDelegateBase内部生成这个对象.

 类似资料: