UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MulticastDelegateBase.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "AutoRTFM.h"
6#include "CoreTypes.h"
8#include "Containers/Array.h"
10#include "Delegates/IDelegateInstance.h" // IWYU pragma: export
11#include "Delegates/DelegateBase.h" // IWYU pragma: export
12
13#if !defined(NUM_MULTICAST_DELEGATE_INLINE_ENTRIES) || NUM_MULTICAST_DELEGATE_INLINE_ENTRIES == 0
15#elif NUM_MULTICAST_DELEGATE_INLINE_ENTRIES < 0
16 #error NUM_MULTICAST_DELEGATE_INLINE_ENTRIES must be positive
17#else
19#endif
20
21#define UE_MULTICAST_DELEGATE_DEFAULT_COMPACTION_THRESHOLD 2
22
26template<typename UserPolicy>
27class TMulticastDelegateBase : public TDelegateAccessHandlerBase<typename UserPolicy::FThreadSafetyMode>
28{
29protected:
31 using typename Super::FReadAccessScope;
32 using Super::GetReadAccessScope;
33 using typename Super::FWriteAccessScope;
34 using Super::GetWriteAccessScope;
35
36 // individual bindings are not checked for races as it's done for the parent delegate
38
40
41public:
46
48 {
49 if (&Other == this)
50 {
51 return *this;
52 }
53
56
57 {
58 FWriteAccessScope OtherWriteScope = Other.GetWriteAccessScope();
59
60 LocalInvocationList = MoveTemp(Other.InvocationList);
61 LocalCompactionThreshold = Other.CompactionThreshold;
63 checkf(Other.InvocationListLockCount == 0, TEXT("Moving from a multicast delegate while it is mid broadcast"));
64 }
65
66 {
67 FWriteAccessScope ThisWriteScope = GetWriteAccessScope();
68
69 ClearUnchecked();
70 InvocationList = MoveTemp(LocalInvocationList);
71 CompactionThreshold = LocalCompactionThreshold;
72 checkf(InvocationListLockCount == 0, TEXT("Moving to a multicast delegate while it is mid broadcast"));
73 }
74
75 return *this;
76 }
77
79 void Clear( )
80 {
81 FWriteAccessScope WriteScope = GetWriteAccessScope();
82
83 ClearUnchecked();
84 }
85
91 inline bool IsBound( ) const
92 {
93 FReadAccessScope ReadScope = GetReadAccessScope();
94
95 for (const UnicastDelegateType& DelegateBaseRef : InvocationList)
96 {
97 if (DelegateBaseRef.GetDelegateInstanceProtected())
98 {
99 return true;
100 }
101 }
102 return false;
103 }
104
111 {
112 FReadAccessScope ReadScope = GetReadAccessScope();
113
114 for (const UnicastDelegateType& DelegateBaseRef : InvocationList)
115 {
116 const IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
118
119 if ((DelegateInstance != nullptr) && DelegateInstance->HasSameObject(InUserObject))
120 {
121 return true;
122 }
123 }
124
125 return false;
126 }
127
136 {
137 FWriteAccessScope WriteScope = GetWriteAccessScope();
138
139 int32 Result = 0;
140 if (ShouldSkipCompactionUnderAutoRTFM() || (InvocationListLockCount > 0))
141 {
142 for (UnicastDelegateType& DelegateBaseRef : InvocationList)
143 {
144 IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
146
147 if ((DelegateInstance != nullptr) && DelegateInstance->HasSameObject(InUserObject))
148 {
149 // Manually unbind the delegate here so the compaction will find and remove it.
150 DelegateBaseRef.Unbind();
151 ++Result;
152 }
153 }
154
155 // can't compact at the moment as the invocation list is locked, but set out threshold to zero so the next add will do it
156 if (Result > 0)
157 {
158 CompactionThreshold = 0;
159 }
160 }
161 else
162 {
163 // compact us while shuffling in later delegates to fill holes
164 for (int32 InvocationListIndex = 0; InvocationListIndex < InvocationList.Num();)
165 {
167
168 IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
170
171 if (DelegateInstance == nullptr
172 || DelegateInstance->HasSameObject(InUserObject)
173 || DelegateInstance->IsCompactable())
174 {
176 ++Result;
177 }
178 else
179 {
181 }
182 }
183
184 CompactionThreshold = FMath::Max(UE_MULTICAST_DELEGATE_DEFAULT_COMPACTION_THRESHOLD, 2 * InvocationList.Num());
185
186 InvocationList.Shrink();
187 }
188
189 return Result;
190 }
191
196 {
197 FReadAccessScope ReadScope = GetReadAccessScope();
198
199 SIZE_T Size = 0;
200 Size += InvocationList.GetAllocatedSize();
201 for (const UnicastDelegateType& DelegateBaseRef : InvocationList)
202 {
203 Size += DelegateBaseRef.GetAllocatedSize();
204 }
205 return Size;
206 }
207
208#if UE_DELEGATE_CHECK_LIFETIME
209
216 {
218
219 FReadAccessScope ReadScope = GetReadAccessScope();
220
221 for (const UnicastDelegateType& DelegateBaseRef : InvocationList)
222 {
223 const IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
225
226 if (DelegateInstance != nullptr)
227 {
229
231 TrackedDelegateInstance->GetBoundFunctionName(),
232 TrackedDelegateInstance->GetModuleName()));
233 }
234 }
235
237 }
238
239#endif // UE_DELEGATE_CHECK_LIFETIME
240
241protected:
242
246 , InvocationListLockCount(0)
247 {
248 }
249
250protected:
251 template<typename DelegateInstanceInterfaceType>
253 {
255
256 {
257 FReadAccessScope OtherReadScope = Other.GetReadAccessScope();
258
259 for (const UnicastDelegateType& OtherDelegateRef : Other.GetInvocationList())
260 {
261 if (const IDelegateInstance* OtherInstance = OtherDelegateRef.GetDelegateInstanceProtected())
262 {
264
266 static_cast<const DelegateInstanceInterfaceType*>(OtherInstance)->CreateCopy(TempDelegate);
268 }
269 }
270 }
271
272 {
273 FWriteAccessScope ThisWriteScope = GetWriteAccessScope();
274
275 ClearUnchecked();
276 InvocationList = MoveTemp(TempInvocationList);
277 }
278 }
279
280 template<typename DelegateInstanceInterfaceType, typename... ParamTypes>
281 void Broadcast(ParamTypes... Params) const
282 {
283 // the `const` on the method is a lie
284 FWriteAccessScope WriteScope = const_cast<TMulticastDelegateBase*>(this)->GetWriteAccessScope();
285
286 bool NeedsCompaction = false;
287
288 LockInvocationList();
289 {
290 const InvocationListType& LocalInvocationList = GetInvocationList();
291
292 // call bound functions in reverse order, so we ignore any instances that may be added by callees
294 {
295 // this down-cast is OK! allows for managing invocation list in the base class without requiring virtual functions
297
298 const IDelegateInstance* DelegateInstanceInterface = DelegateBase.GetDelegateInstanceProtected();
300
301 if (DelegateInstanceInterface == nullptr || !static_cast<const DelegateInstanceInterfaceType*>(DelegateInstanceInterface)->ExecuteIfSafe(Params...))
302 {
303 NeedsCompaction = true;
304 }
305 }
306 }
307 UnlockInvocationList();
308
309 if (NeedsCompaction)
310 {
311 const_cast<TMulticastDelegateBase*>(this)->CompactInvocationList();
312 }
313 }
314
320 template <typename NewDelegateType>
322 {
323 FWriteAccessScope WriteScope = GetWriteAccessScope();
324
325 FDelegateHandle Result;
326 if (NewDelegateBaseRef.IsBound())
327 {
328 // compact but obey threshold of when this will trigger
329 CompactInvocationList(true);
330 Result = NewDelegateBaseRef.GetHandle();
332 }
333 return Result;
334 }
335
343 {
344 FWriteAccessScope WriteScope = GetWriteAccessScope();
345
347 {
349
350 IDelegateInstance* DelegateInstance = DelegateBase.GetDelegateInstanceProtected();
352
353 if (DelegateInstance && DelegateInstance->GetHandle() == Handle)
354 {
355 DelegateBase.Unbind();
356 CompactInvocationList();
357 return true; // each delegate binding has a unique handle, so once we find it, we can stop
358 }
359 }
360
361 return false;
362 }
363
364private:
365
366 inline bool ShouldSkipCompactionUnderAutoRTFM() const
367 {
368 // As a performance optimization for the AutoRTFM runtime, we *always*
369 // skip compacting a multicast delegate when called via the AutoRTFM
370 // runtime. We skip calls both in the bodies of transactions themselves,
371 // and also those that are called as part of on-commit or on-abort calls.
372 return AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting();
373 }
374
380 void CompactInvocationList(bool CheckThreshold=false)
381 {
382 if (ShouldSkipCompactionUnderAutoRTFM())
383 {
384 return;
385 }
386
387 // if locked and no object, just return
388 if (InvocationListLockCount > 0)
389 {
390 return;
391 }
392
393 // if checking threshold, obey but decay. This is to ensure that even infrequently called delegates will
394 // eventually compact during an Add()
395 if (CheckThreshold && --CompactionThreshold > InvocationList.Num())
396 {
397 return;
398 }
399
400 int32 OldNumItems = InvocationList.Num();
401
402 // Find anything null or compactable and remove it
403 for (int32 InvocationListIndex = 0; InvocationListIndex < InvocationList.Num();)
404 {
405 auto& DelegateBaseRef = InvocationList[InvocationListIndex];
406
407 IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
409
410 if (DelegateInstance == nullptr || DelegateInstance->IsCompactable())
411 {
413 }
414 else
415 {
417 }
418 }
419
420 CompactionThreshold = FMath::Max(UE_MULTICAST_DELEGATE_DEFAULT_COMPACTION_THRESHOLD, 2 * InvocationList.Num());
421
422 if (OldNumItems > CompactionThreshold)
423 {
424 // would be nice to shrink down to threshold, but reserve only grows..?
425 InvocationList.Shrink();
426 }
427 }
428
434 inline InvocationListType& GetInvocationList( )
435 {
436 return InvocationList;
437 }
438
439 inline const InvocationListType& GetInvocationList( ) const
440 {
441 return InvocationList;
442 }
443
445 inline void LockInvocationList( ) const
446 {
447 if (ShouldSkipCompactionUnderAutoRTFM())
448 {
449 return;
450 }
451
452 ++InvocationListLockCount;
453 }
454
456 inline void UnlockInvocationList( ) const
457 {
458 if (ShouldSkipCompactionUnderAutoRTFM())
459 {
460 return;
461 }
462
463 --InvocationListLockCount;
464 }
465
467 inline int32 GetInvocationListLockCount() const
468 {
469 return InvocationListLockCount;
470 }
471
472private:
473 void ClearUnchecked()
474 {
475 for (UnicastDelegateType& DelegateBaseRef : InvocationList)
476 {
477#if UE_DELEGATE_CHECK_LIFETIME
478 IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected();
480#endif // UE_DELEGATE_CHECK_LIFETIME
481
482 DelegateBaseRef.Unbind();
483 }
484
485 CompactInvocationList(false);
486 }
487
489 InvocationListType InvocationList;
490
493
495 mutable int32 InvocationListLockCount = 0;
496};
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define CHECK_DELEGATE_LIFETIME(DelegateInstance)
Definition DelegateBase.h:116
const void * FDelegateUserObjectConst
Definition IDelegateInstance.h:108
FHeapAllocator FMulticastInvocationListAllocatorType
Definition MulticastDelegateBase.h:14
#define UE_MULTICAST_DELEGATE_DEFAULT_COMPACTION_THRESHOLD
Definition MulticastDelegateBase.h:21
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint32 Size
Definition VulkanMemory.cpp:4034
Definition IDelegateInstance.h:14
Definition IDelegateInstance.h:112
UE_REWRITE SizeType Num() const
Definition Array.h:1144
UE_FORCEINLINE_HINT void RemoveAtSwap(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2185
UE_FORCEINLINE_HINT SizeType Emplace(ArgsType &&... Args)
Definition Array.h:2561
UE_NODEBUG UE_FORCEINLINE_HINT SIZE_T GetAllocatedSize(void) const
Definition Array.h:1059
UE_FORCEINLINE_HINT void Shrink()
Definition Array.h:1278
Definition DelegateAccessHandler.h:30
Definition DelegateBase.h:226
Definition MulticastDelegateBase.h:28
bool IsBoundToObject(FDelegateUserObjectConst InUserObject) const
Definition MulticastDelegateBase.h:110
SIZE_T GetAllocatedSize() const
Definition MulticastDelegateBase.h:195
FDelegateHandle AddDelegateInstance(NewDelegateType &&NewDelegateBaseRef)
Definition MulticastDelegateBase.h:321
TMulticastDelegateBase(TMulticastDelegateBase &&Other)
Definition MulticastDelegateBase.h:42
int32 RemoveAll(FDelegateUserObjectConst InUserObject)
Definition MulticastDelegateBase.h:135
void Clear()
Definition MulticastDelegateBase.h:79
bool RemoveDelegateInstance(FDelegateHandle Handle)
Definition MulticastDelegateBase.h:342
TArray< UnicastDelegateType, FMulticastInvocationListAllocatorType > InvocationListType
Definition MulticastDelegateBase.h:39
TDelegateBase< FNotThreadSafeNotCheckedDelegateMode > UnicastDelegateType
Definition MulticastDelegateBase.h:37
TMulticastDelegateBase & operator=(TMulticastDelegateBase &&Other)
Definition MulticastDelegateBase.h:47
bool IsBound() const
Definition MulticastDelegateBase.h:91
void Broadcast(ParamTypes... Params) const
Definition MulticastDelegateBase.h:281
TMulticastDelegateBase()
Definition MulticastDelegateBase.h:244
void CopyFrom(const TMulticastDelegateBase &Other)
Definition MulticastDelegateBase.h:252
Definition ContainerAllocationPolicies.h:618
Definition ContainerAllocationPolicies.h:894
Definition Tuple.h:652