EM_Task/CoreUObject/Public/UObject/SparseDelegate.h

444 lines
25 KiB
C
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h"
#include "Delegates/Delegate.h"
#include "Misc/ScopeLock.h"
struct FSparseDelegate;
/**
* Sparse delegates can be used for infrequently bound dynamic delegates so that the object uses only
* 1 byte of storage instead of having the full overhead of the delegate invocation list.
* The cost to invoke, add, remove, etc. from the delegate is higher than using the delegate
* directly and thus the memory savings benefit should be traded off against the frequency
* with which you would expect the delegate to be bound.
*/
/** Helper class for handling sparse delegate bindings */
struct COREUOBJECT_API FSparseDelegateStorage
{
public:
/** Registers the sparse delegate so that the offset can be determined. */
static void RegisterDelegateOffset(const UObject* OwningObject, FName DelegateName, size_t OffsetToOwner);
/** Binds a sparse delegate to the owner. Returns if the delegate was successfully bound. */
static bool Add(const UObject* DelegateOwner, const FName DelegateName, FScriptDelegate Delegate);
/** Binds a sparse delegate to the owner, verifying first that the delegate is not already bound. Returns if the delegate was successfully bound. */
static bool AddUnique(const UObject* DelegateOwner, const FName DelegateName, FScriptDelegate Delegate);
/** Returns whether a sparse delegate is bound to the owner. */
static bool Contains(const UObject* DelegateOwner, const FName DelegateName, const FScriptDelegate& Delegate);
static bool Contains(const UObject* DelegateOwner, const FName DelegateName, const UObject* InObject, FName InFunctionName);
/** Removes a delegate binding from the owner's sparse delegate storage. Returns true if there are still bindings to the delegate. */
static bool Remove(const UObject* DelegateOwner, const FName DelegateName, const FScriptDelegate& Delegate);
static bool Remove(const UObject* DelegateOwner, const FName DelegateName, const UObject* InObject, FName InFunctionName);
/** Removes all sparse delegate binding from the owner for a given object. Returns true if there are still bindings to the delegate. */
static bool RemoveAll(const UObject* DelegateOwner, const FName DelegateName, const UObject* UserObject);
/** Clear all of the named sparse delegate bindings from the owner. */
static void Clear(const UObject* DelegateOwner, const FName DelegateName);
/** Acquires the actual Multicast Delegate from the annotation if any delegates are bound to it. Will be null if no entry exists in the annotation for this object/delegatename. */
static FMulticastScriptDelegate* GetMulticastDelegate(const UObject* DelegateOwner, const FName DelegateName);
/** Acquires the actual Multicast Delegate from the annotation if any delegates are bound to it as a shared pointer. Will be null if no entry exists in the annotation for this object/delegatename. */
static TSharedPtr<FMulticastScriptDelegate> GetSharedMulticastDelegate(const UObject* DelegateOwner, const FName DelegateName);
/** Directly sets the Multicast Delegate for this object/delegatename pair. If the delegate is unbound it will be assigned/inserted anyways. */
static void SetMulticastDelegate(const UObject* DelegateOwner, const FName DelegateName, FMulticastScriptDelegate Delegate);
/** Using the registry of sparse delegates recover the FSparseDelegate address from the UObject and name. */
static FSparseDelegate* ResolveSparseDelegate(const UObject* OwningObject, FName DelegateName);
/** Using the registry of sparse delegates recover the UObject owner from the FSparseDelegate address owning class and delegate names. */
static UObject* ResolveSparseOwner(const FSparseDelegate& SparseDelegate, const FName OwningClassName, const FName DelegateName);
/** Outputs a report about which delegates are bound. */
static void SparseDelegateReport(const TArray<FString>&, UWorld*, FOutputDevice&);
private:
struct FObjectListener: public FUObjectArray::FUObjectDeleteListener
{
virtual ~FObjectListener();
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) override;
virtual void OnUObjectArrayShutdown();
void EnableListener();
void DisableListener();
};
/** Allow the object listener to use the critical section and remove objects from the map */
friend struct FObjectListener;
/** A listener to get notified when objects have been deleted and remove them from the map */
static FObjectListener SparseDelegateObjectListener;
/** Critical Section for locking access to the sparse delegate map */
static FCriticalSection SparseDelegateMapCritical;
/** Delegate map is a map of Delegate names to a shared pointer of the multicast script delegate */
typedef TMap<FName, TSharedPtr<FMulticastScriptDelegate>> FSparseDelegateMap;
/** Map of objects to the map of delegates that are bound to that object */
static TMap<const UObjectBase*, FSparseDelegateMap> SparseDelegates;
/** Sparse delegate offsets are indexed by ActorClass/DelegateName pair */
static TMap<TPair<FName, FName>, size_t> SparseDelegateObjectOffsets;
};
/** Base implementation for all sparse delegate types */
struct FSparseDelegate
{
public:
FSparseDelegate()
: bIsBound(false)
{
}
/**
* Checks to see if any functions are bound to this multi-cast delegate
*
* @return True if any functions are bound
*/
bool IsBound() const
{
return bIsBound;
}
/**
* Adds a function delegate to this multi-cast delegate's invocation list if a delegate with the same signature
* doesn't already exist in the invocation list
*
* @param DelegateOwner UObject that owns the resolved sparse delegate
* @param DelegateName Name of the resolved sparse delegate
* @param InDelegate Delegate to bind to the sparse delegate
*
* NOTE: Only call this function from blueprint sparse delegate infrastructure on a resolved generic FScriptDelegate pointer.
* Generally from C++ you should call AddUnique() directly.
*/
void __Internal_AddUnique(const UObject* DelegateOwner, FName DelegateName, FScriptDelegate InDelegate)
{
bIsBound |= FSparseDelegateStorage::AddUnique(DelegateOwner, DelegateName, MoveTemp(InDelegate));
}
/**
* Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the
* order of the delegates may not be preserved!
*
* @param DelegateOwner UObject that owns the resolved sparse delegate
* @param DelegateName Name of the resolved sparse delegate
* @param InDelegate Delegate to remove from the sparse delegate
*
* NOTE: Only call this function from blueprint sparse delegate infrastructure on a resolved generic FScriptDelegate pointer.
* Generally from C++ you should call Remove() directly.
*/
void __Internal_Remove(const UObject* DelegateOwner, FName DelegateName, const FScriptDelegate& InDelegate)
{
if (bIsBound)
{
bIsBound = FSparseDelegateStorage::Remove(DelegateOwner, DelegateName, InDelegate);
}
}
/**
* Removes all functions from this delegate's invocation list
*
* @param DelegateOwner UObject that owns the resolved sparse delegate
* @param DelegateName Name of the resolved sparse delegate
*
* NOTE: Only call this function from blueprint sparse delegate infrastructure on a resolved generic FScriptDelegate pointer.
* Generally from C++ you should call Clear() directly.
*/
void __Internal_Clear(const UObject* DelegateOwner, FName DelegateName)
{
if (bIsBound)
{
FSparseDelegateStorage::Clear(DelegateOwner, DelegateName);
bIsBound = false;
}
}
protected:
friend class FMulticastSparseDelegateProperty;
bool bIsBound;
};
/** Sparse version of TBaseDynamicDelegate */
template <typename MulticastDelegate, typename OwningClass, typename DelegateInfoClass>
struct TSparseDynamicDelegate: public FSparseDelegate
{
public:
typedef typename MulticastDelegate::FDelegate FDelegate;
protected:
FName GetDelegateName() const
{
static const FName DelegateFName(DelegateInfoClass::GetDelegateName());
return DelegateFName;
}
private:
UObject* GetDelegateOwner() const
{
const size_t OffsetToOwner = DelegateInfoClass::template GetDelegateOffset<OwningClass>();
check(OffsetToOwner);
UObject* DelegateOwner = reinterpret_cast<UObject*>((uint8*)this - OffsetToOwner);
check(DelegateOwner->IsValidLowLevelFast(false)); // Most likely the delegate is trying to be used on the stack, in an object it wasn't defined for, or for a class member with a different name than it was defined for. It is only valid for a sparse delegate to be used for the exact class/property name it is defined with.
return DelegateOwner;
}
public:
/** Returns the multicast delegate if any delegates are bound to the sparse delegate */
UE_DEPRECATED(4.25, "This function has been deprecated - please use GetShared() instead")
MulticastDelegate* Get() const
{
MulticastDelegate* Result = nullptr;
if (bIsBound)
{
Result = static_cast<MulticastDelegate*>(FSparseDelegateStorage::GetMulticastDelegate(GetDelegateOwner(), GetDelegateName()));
}
return Result;
}
/** Returns the multicast delegate if any delegates are bound to the sparse delegate */
TSharedPtr<MulticastDelegate> GetShared() const
{
TSharedPtr<MulticastDelegate> Result;
if (bIsBound)
{
Result = StaticCastSharedPtr<MulticastDelegate>(FSparseDelegateStorage::GetSharedMulticastDelegate(GetDelegateOwner(), GetDelegateName()));
}
return Result;
}
/**
* Checks whether a function delegate is already a member of this multi-cast delegate's invocation list
*
* @param InDelegate Delegate to check
* @return True if the delegate is already in the list.
*/
bool Contains(const FScriptDelegate& InDelegate) const
{
return (bIsBound ? FSparseDelegateStorage::Contains(GetDelegateOwner(), GetDelegateName(), InDelegate) : false);
}
/**
* Checks whether a function delegate is already a member of this multi-cast delegate's invocation list
*
* @param InObject Object of the delegate to check
* @param InFunctionName Function name of the delegate to check
* @return True if the delegate is already in the list.
*/
bool Contains(const UObject* InObject, FName InFunctionName) const
{
return (bIsBound ? FSparseDelegateStorage::Contains(GetDelegateOwner(), GetDelegateName(), InObject, InFunctionName) : false);
}
/**
* Adds a function delegate to this multi-cast delegate's invocation list
*
* @param InDelegate Delegate to add
*/
void Add(FScriptDelegate InDelegate)
{
bIsBound |= FSparseDelegateStorage::Add(GetDelegateOwner(), GetDelegateName(), MoveTemp(InDelegate));
}
/**
* Adds a function delegate to this multi-cast delegate's invocation list if a delegate with the same signature
* doesn't already exist in the invocation list
*
* @param InDelegate Delegate to add
*/
void AddUnique(FScriptDelegate InDelegate)
{
FSparseDelegate::__Internal_AddUnique(GetDelegateOwner(), GetDelegateName(), MoveTemp(InDelegate));
}
/**
* Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the
* order of the delegates may not be preserved!
*
* @param InDelegate Delegate to remove
*/
void Remove(const FScriptDelegate& InDelegate)
{
FSparseDelegate::__Internal_Remove(GetDelegateOwner(), GetDelegateName(), InDelegate);
}
/**
* Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the
* order of the delegates may not be preserved!
*
* @param InObject Object of the delegate to remove
* @param InFunctionName Function name of the delegate to remove
*/
void Remove(const UObject* InObject, FName InFunctionName)
{
if (bIsBound)
{
bIsBound = FSparseDelegateStorage::Remove(GetDelegateOwner(), GetDelegateName(), InObject, InFunctionName);
}
}
/**
* Removes all delegate bindings from this multicast delegate's
* invocation list that are bound to the specified object.
*
* This method also compacts the invocation list.
*
* @param InObject The object to remove bindings for.
*/
void RemoveAll(const UObject* Object)
{
if (bIsBound)
{
bIsBound = FSparseDelegateStorage::RemoveAll(GetDelegateOwner(), GetDelegateName(), Object);
}
}
/**
* Removes all functions from this delegate's invocation list
*/
void Clear()
{
FSparseDelegate::__Internal_Clear(GetDelegateOwner(), GetDelegateName());
}
/**
* Broadcasts this delegate to all bound objects, except to those that may have expired.
*/
template <typename... ParamTypes>
void Broadcast(ParamTypes... Params)
{
if (TSharedPtr<MulticastDelegate> MCDelegate = GetShared())
{
MCDelegate->Broadcast(Params...);
}
}
/**
* Tests if a UObject instance and a UObject method address pair are already bound to this multi-cast delegate.
*
* @param InUserObject UObject instance
* @param InMethodPtr Member function address pointer
* @param InFunctionName Name of member function, without class name
* @return True if the instance/method is already bound.
*
* NOTE: Do not call this function directly. Instead, call IsAlreadyBound() which is a macro proxy function that
* automatically sets the function name string for the caller.
*/
template <class UserClass>
bool __Internal_IsAlreadyBound(UserClass* InUserObject, typename FDelegate::template TMethodPtrResolver<UserClass>::FMethodPtr InMethodPtr, FName InFunctionName) const
{
check(InUserObject != nullptr && InMethodPtr != nullptr);
// NOTE: We're not actually using the incoming method pointer or calling it. We simply require it for type-safety reasons.
return this->Contains(InUserObject, InFunctionName);
}
/**
* Binds a UObject instance and a UObject method address to this multi-cast delegate.
*
* @param InUserObject UObject instance
* @param InMethodPtr Member function address pointer
* @param InFunctionName Name of member function, without class name
*
* NOTE: Do not call this function directly. Instead, call AddDynamic() which is a macro proxy function that
* automatically sets the function name string for the caller.
*/
template <class UserClass>
void __Internal_AddDynamic(UserClass* InUserObject, typename FDelegate::template TMethodPtrResolver<UserClass>::FMethodPtr InMethodPtr, FName InFunctionName)
{
check(InUserObject != nullptr && InMethodPtr != nullptr);
// NOTE: We're not actually storing the incoming method pointer or calling it. We simply require it for type-safety reasons.
FDelegate NewDelegate;
NewDelegate.__Internal_BindDynamic(InUserObject, InMethodPtr, InFunctionName);
this->Add(NewDelegate);
}
/**
* Binds a UObject instance and a UObject method address to this multi-cast delegate, but only if it hasn't been bound before.
*
* @param InUserObject UObject instance
* @param InMethodPtr Member function address pointer
* @param InFunctionName Name of member function, without class name
*
* NOTE: Do not call this function directly. Instead, call AddUniqueDynamic() which is a macro proxy function that
* automatically sets the function name string for the caller.
*/
template <class UserClass>
void __Internal_AddUniqueDynamic(UserClass* InUserObject, typename FDelegate::template TMethodPtrResolver<UserClass>::FMethodPtr InMethodPtr, FName InFunctionName)
{
check(InUserObject != nullptr && InMethodPtr != nullptr);
// NOTE: We're not actually storing the incoming method pointer or calling it. We simply require it for type-safety reasons.
FDelegate NewDelegate;
NewDelegate.__Internal_BindDynamic(InUserObject, InMethodPtr, InFunctionName);
this->AddUnique(NewDelegate);
}
/**
* Unbinds a UObject instance and a UObject method address from this multi-cast delegate.
*
* @param InUserObject UObject instance
* @param InMethodPtr Member function address pointer
* @param InFunctionName Name of member function, without class name
*
* NOTE: Do not call this function directly. Instead, call RemoveDynamic() which is a macro proxy function that
* automatically sets the function name string for the caller.
*/
template <class UserClass>
void __Internal_RemoveDynamic(UserClass* InUserObject, typename FDelegate::template TMethodPtrResolver<UserClass>::FMethodPtr InMethodPtr, FName InFunctionName)
{
check(InUserObject != nullptr && InMethodPtr != nullptr);
// NOTE: We're not actually storing the incoming method pointer or calling it. We simply require it for type-safety reasons.
this->Remove(InUserObject, InFunctionName);
}
private:
// Hide internal functions that never need to be called on the derived classes
void __Internal_AddUnique(const UObject*, FName, FScriptDelegate);
void __Internal_Remove(const UObject*, FName, const FScriptDelegate&);
void __Internal_Clear(const UObject*, FName);
};
#define FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(SparseDelegateClassName, OwningClass, DelegateName, FuncParamList, FuncParamPassThru, ...) \
FUNC_DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWeakObjectPtr, SparseDelegateClassName##_MCSignature, SparseDelegateClassName##_DelegateWrapper, FUNC_CONCAT(FuncParamList), FUNC_CONCAT(FuncParamPassThru), __VA_ARGS__) \
struct SparseDelegateClassName##InfoGetter \
{ \
static const char* GetDelegateName() { return #DelegateName; } \
template<typename T> \
static size_t GetDelegateOffset() { return offsetof(T, DelegateName); } \
}; \
struct SparseDelegateClassName : public TSparseDynamicDelegate<SparseDelegateClassName##_MCSignature, OwningClass, SparseDelegateClassName##InfoGetter> \
{ \
};
/** Declares a sparse blueprint-accessible broadcast delegate that can bind to multiple native UFUNCTIONs simultaneously */
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(SparseDelegateClass, OwningClass, DelegateName) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, , FUNC_CONCAT( *this ), void )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1 ), FUNC_CONCAT( *this, InParam1 ), void, Param1Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2 ), FUNC_CONCAT( *this, InParam1, InParam2 ), void, Param1Type, Param2Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_ThreeParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3 ), void, Param1Type, Param2Type, Param3Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4 ), void, Param1Type, Param2Type, Param3Type, Param4Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4, Param5Type InParam5 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4, InParam5 ), void, Param1Type, Param2Type, Param3Type, Param4Type, Param5Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name, Param6Type, Param6Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4, Param5Type InParam5, Param6Type InParam6 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4, InParam5, InParam6 ), void, Param1Type, Param2Type, Param3Type, Param4Type, Param5Type, Param6Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SevenParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name, Param6Type, Param6Name, Param7Type, Param7Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4, Param5Type InParam5, Param6Type InParam6, Param7Type InParam7 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4, InParam5, InParam6, InParam7 ), void, Param1Type, Param2Type, Param3Type, Param4Type, Param5Type, Param6Type, Param7Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_EightParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name, Param6Type, Param6Name, Param7Type, Param7Name, Param8Type, Param8Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4, Param5Type InParam5, Param6Type InParam6, Param7Type InParam7, Param8Type InParam8 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4, InParam5, InParam6, InParam7, InParam8 ), void, Param1Type, Param2Type, Param3Type, Param4Type, Param5Type, Param6Type, Param7Type, Param8Type )
#define DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_NineParams(SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name, Param6Type, Param6Name, Param7Type, Param7Name, Param8Type, Param8Name, Param9Type, Param9Name) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE( SparseDelegateClass, OwningClass, DelegateName, FUNC_CONCAT( Param1Type InParam1, Param2Type InParam2, Param3Type InParam3, Param4Type InParam4, Param5Type InParam5, Param6Type InParam6, Param7Type InParam7, Param8Type InParam8, Param9Type InParam9 ), FUNC_CONCAT( *this, InParam1, InParam2, InParam3, InParam4, InParam5, InParam6, InParam7, InParam8, InParam9 ), void, Param1Type, Param2Type, Param3Type, Param4Type, Param5Type, Param6Type, Param7Type, Param8Type, Param9Type )