EM_Task/CoreUObject/Public/UObject/UObjectAnnotation.h
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

1326 lines
41 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
UObjectAnnotation.h: Unreal object annotation template
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "UObject/UObjectArray.h"
#include "Misc/ScopeLock.h"
#include "Misc/ScopeRWLock.h"
/**
* FUObjectAnnotationSparse is a helper class that is used to store sparse, slow, temporary, editor only, external
* or other low priority information about UObjects.
*
* There is a notion of a default annotation and UObjects default to this annotation and this takes no storage.
*
* Annotations are automatically cleaned up when UObjects are destroyed.
* Annotation are not "garbage collection aware", so it isn't safe to store pointers to other UObjects in an
* annotation unless external guarantees are made such that destruction of the other object removes the
* annotation.
* @param TAnnotation type of the annotation
* @param bAutoRemove if true, annotation will automatically be removed, otherwise in non-final builds it will verify that the annotation was removed by other means prior to destruction.
**/
template <typename TAnnotation, bool bAutoRemove>
class FUObjectAnnotationSparse: public FUObjectArray::FUObjectDeleteListener
{
public:
/**
* Interface for FUObjectAllocator::FUObjectDeleteListener
*
* @param Object object that has been destroyed
* @param Index index of object that is being deleted
*/
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) override
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (!bAutoRemove)
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
// in this case we are only verifying that the external assurances of removal are met
check(!AnnotationMap.Find(Object));
}
else
#endif
{
RemoveAnnotation(Object);
}
}
virtual void OnUObjectArrayShutdown() override
{
RemoveAllAnnotations();
GUObjectArray.RemoveUObjectDeleteListener(this);
}
/**
* Constructor, initializes to nothing
*/
FUObjectAnnotationSparse(): AnnotationCacheKey(NULL)
{
// default constructor is required to be default annotation
check(AnnotationCacheValue.IsDefault());
}
/**
* Destructor, removes all annotations, which removes the annotation as a uobject destruction listener
*/
virtual ~FUObjectAnnotationSparse()
{
RemoveAllAnnotations();
}
private:
template <typename T>
void AddAnnotationInternal(const UObjectBase* Object, T&& Annotation)
{
check(Object);
TAnnotation LocalAnnotation = Forward<T>(Annotation);
if (LocalAnnotation.IsDefault())
{
RemoveAnnotation(Object); // adding the default annotation is the same as removing an annotation
}
else
{
bool bWasEmpty = false;
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
AnnotationCacheKey = Object;
AnnotationCacheValue = MoveTemp(LocalAnnotation);
bWasEmpty = (AnnotationMap.Num() == 0);
AnnotationMap.Add(AnnotationCacheKey, AnnotationCacheValue);
}
if (bWasEmpty)
{
// we are adding the first one, so if we are auto removing or verifying removal, register now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.AddUObjectDeleteListener(this);
}
}
}
}
public:
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Object Object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(const UObjectBase* Object, TAnnotation&& Annotation)
{
AddAnnotationInternal(Object, MoveTemp(Annotation));
}
void AddAnnotation(const UObjectBase* Object, const TAnnotation& Annotation)
{
AddAnnotationInternal(Object, Annotation);
}
/**
* Removes an annotation from the annotation list and returns the annotation if it had one
*
* @param Object Object to de-annotate.
* @return Old annotation
*/
TAnnotation GetAndRemoveAnnotation(const UObjectBase* Object)
{
check(Object);
bool bHadElements = false;
bool bIsNowEmpty = false;
// Avoid holding the lock while we call GUObjectArray.RemoveUObjectDeleteListener as it could deadlock
TAnnotation Result;
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
AnnotationCacheKey = Object;
AnnotationCacheValue = TAnnotation();
bHadElements = (AnnotationMap.Num() > 0);
AnnotationMap.RemoveAndCopyValue(AnnotationCacheKey, Result);
bIsNowEmpty = (AnnotationMap.Num() == 0);
}
if (bHadElements && bIsNowEmpty)
{
// we are removing the last one, so if we are auto removing or verifying removal, unregister now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
return Result;
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(const UObjectBase* Object)
{
check(Object);
bool bHadElements = false;
bool bIsNowEmpty = false;
// Avoid holding the lock while we call GUObjectArray.RemoveUObjectDeleteListener as it could deadlock
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
AnnotationCacheKey = Object;
AnnotationCacheValue = TAnnotation();
bHadElements = (AnnotationMap.Num() > 0);
AnnotationMap.Remove(AnnotationCacheKey);
bIsNowEmpty = (AnnotationMap.Num() == 0);
}
if (bHadElements && bIsNowEmpty)
{
// we are removing the last one, so if we are auto removing or verifying removal, unregister now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
}
/**
* Removes all annotation from the annotation list.
*
*/
void RemoveAllAnnotations()
{
bool bHadElements = false;
// Avoid holding the lock while we call GUObjectArray.RemoveUObjectDeleteListener as it could deadlock
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
AnnotationCacheKey = nullptr;
AnnotationCacheValue = TAnnotation();
bHadElements = (AnnotationMap.Num() > 0);
AnnotationMap.Empty();
}
if (bHadElements)
{
// we are removing the last one, so if we are auto removing or verifying removal, unregister now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
}
/**
* Return the annotation associated with a uobject
*
* @param Object Object to return the annotation for
*/
FORCEINLINE TAnnotation GetAnnotation(const UObjectBase* Object)
{
check(Object);
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
if (Object != AnnotationCacheKey)
{
AnnotationCacheKey = Object;
TAnnotation* Entry = AnnotationMap.Find(AnnotationCacheKey);
if (Entry)
{
AnnotationCacheValue = *Entry;
}
else
{
AnnotationCacheValue = TAnnotation();
}
}
return AnnotationCacheValue;
}
/**
* Return the annotation map. Caution, this is for low level use
* @return A mapping from UObjectBase to annotation for non-default annotations
*/
const TMap<const UObjectBase*, TAnnotation>& GetAnnotationMap() const
{
return AnnotationMap;
}
/**
* Reserves memory for the annotation map for the specified number of elements, used to avoid reallocations.
*/
void Reserve(int32 ExpectedNumElements)
{
FScopeLock AnnotationMapLock(&AnnotationMapCritical);
AnnotationMap.Empty(ExpectedNumElements);
}
private:
/**
* Map from live objects to an annotation
*/
TMap<const UObjectBase*, TAnnotation> AnnotationMap;
FCriticalSection AnnotationMapCritical;
/**
* Key for a one-item cache of the last lookup into AnnotationMap.
* Annotation are often called back-to-back so this is a performance optimization for that.
*/
const UObjectBase* AnnotationCacheKey;
/**
* Value for a one-item cache of the last lookup into AnnotationMap.
*/
TAnnotation AnnotationCacheValue;
};
/**
* FUObjectAnnotationSparseSearchable is a helper class that is used to store sparse, slow, temporary, editor only, external
* or other low priority information about UObjects...and also provides the ability to find a object based on the unique
* annotation.
*
* All of the restrictions mentioned for FUObjectAnnotationSparse apply
*
* @param TAnnotation type of the annotation
* @param bAutoRemove if true, annotation will automatically be removed, otherwise in non-final builds it will verify that the annotation was removed by other means prior to destruction.
**/
template <typename TAnnotation, bool bAutoRemove>
class FUObjectAnnotationSparseSearchable: public FUObjectAnnotationSparse<TAnnotation, bAutoRemove>
{
typedef FUObjectAnnotationSparse<TAnnotation, bAutoRemove> Super;
public:
/**
* Interface for FUObjectAllocator::FUObjectDeleteListener
*
* @param Object object that has been destroyed
* @param Index index of object that is being deleted
*/
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) override
{
RemoveAnnotation(Object);
}
virtual void OnUObjectArrayShutdown() override
{
RemoveAllAnnotations();
GUObjectArray.RemoveUObjectDeleteListener(this);
}
/**
* Destructor, removes all annotations, which removes the annotation as a uobject destruction listener
*/
virtual ~FUObjectAnnotationSparseSearchable()
{
RemoveAllAnnotations();
}
/**
* Find the UObject associated with a given annotation
*
* @param Annotation Annotation to find
* @return Object associated with this annotation or NULL if none found
*/
UObject* Find(const TAnnotation& Annotation)
{
FScopeLock InverseAnnotationMapLock(&InverseAnnotationMapCritical);
checkSlow(!Annotation.IsDefault()); // it is not legal to search for the default annotation
return (UObject*)InverseAnnotationMap.FindRef(Annotation);
}
private:
template <typename T>
void AddAnnotationInternal(const UObjectBase* Object, T&& Annotation)
{
FScopeLock InverseAnnotationMapLock(&InverseAnnotationMapCritical);
if (Annotation.IsDefault())
{
RemoveAnnotation(Object); // adding the default annotation is the same as removing an annotation
}
else
{
TAnnotation ExistingAnnotation = this->GetAnnotation(Object);
int32 NumExistingRemoved = InverseAnnotationMap.Remove(ExistingAnnotation);
checkSlow(NumExistingRemoved == 0);
Super::AddAnnotation(Object, Annotation);
// should not exist in the mapping; we require uniqueness
int32 NumRemoved = InverseAnnotationMap.Remove(Annotation);
checkSlow(NumRemoved == 0);
InverseAnnotationMap.Add(Forward<T>(Annotation), Object);
}
}
public:
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Object Object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(const UObjectBase* Object, const TAnnotation& Annotation)
{
AddAnnotationInternal(Object, Annotation);
}
void AddAnnotation(const UObjectBase* Object, TAnnotation&& Annotation)
{
AddAnnotationInternal(Object, MoveTemp(Annotation));
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(const UObjectBase* Object)
{
FScopeLock InverseAnnotationMapLock(&InverseAnnotationMapCritical);
TAnnotation Annotation = this->GetAndRemoveAnnotation(Object);
if (Annotation.IsDefault())
{
// should not exist in the mapping
checkSlow(!InverseAnnotationMap.Find(Annotation));
}
else
{
int32 NumRemoved = InverseAnnotationMap.Remove(Annotation);
checkSlow(NumRemoved == 1);
}
}
/**
* Removes all annotation from the annotation list.
*
*/
void RemoveAllAnnotations()
{
FScopeLock InverseAnnotationMapLock(&InverseAnnotationMapCritical);
Super::RemoveAllAnnotations();
InverseAnnotationMap.Empty();
}
private:
/**
* Inverse Map annotation to live object
*/
TMap<TAnnotation, const UObjectBase*> InverseAnnotationMap;
FCriticalSection InverseAnnotationMapCritical;
};
struct FBoolAnnotation
{
/**
* default constructor
* Default constructor must be the default item
*/
FBoolAnnotation(): Mark(false)
{
}
/**
* Initialization constructor
* @param InMarks marks to initialize to
*/
FBoolAnnotation(bool InMark): Mark(InMark)
{
}
/**
* Determine if this annotation
* @return true is this is a default pair. We only check the linker because CheckInvariants rules out bogus combinations
*/
FORCEINLINE bool IsDefault()
{
return !Mark;
}
/**
* bool associated with an object
*/
bool Mark;
};
template <>
struct TIsPODType<FBoolAnnotation>
{
enum
{
Value = true
};
};
/**
* FUObjectAnnotationSparseBool is a specialization of FUObjectAnnotationSparse for bools, slow, temporary, editor only, external
* or other low priority bools about UObjects.
*
* @todo UE4 this should probably be reimplemented from scratch as a TSet instead of essentially a map to a value that is always true anyway.
**/
class FUObjectAnnotationSparseBool: private FUObjectAnnotationSparse<FBoolAnnotation, true>
{
public:
/**
* Sets this bool annotation to true for this object
*
* @param Object Object to annotate.
*/
FORCEINLINE void Set(const UObjectBase* Object)
{
this->AddAnnotation(Object, FBoolAnnotation(true));
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
FORCEINLINE void Clear(const UObjectBase* Object)
{
this->RemoveAnnotation(Object);
}
/**
* Removes all bool annotations
*
*/
FORCEINLINE void ClearAll()
{
this->RemoveAllAnnotations();
}
/**
* Return the bool annotation associated with a uobject
*
* @param Object Object to return the bool annotation for
*/
FORCEINLINE bool Get(const UObjectBase* Object)
{
return this->GetAnnotation(Object).Mark;
}
/**
* Reserves memory for the annotation map for the specified number of elements, used to avoid reallocations.
*/
FORCEINLINE void Reserve(int32 ExpectedNumElements)
{
FUObjectAnnotationSparse<FBoolAnnotation, true>::Reserve(ExpectedNumElements);
}
FORCEINLINE int32 Num() const
{
return this->GetAnnotationMap().Num();
}
};
/**
* FUObjectAnnotationChunked is a helper class that is used to store dense, fast and temporary, editor only, external
* or other tangential information about subsets of UObjects.
*
* There is a notion of a default annotation and UObjects default to this annotation.
*
* Annotations are automatically returned to the default when UObjects are destroyed.
* Annotation are not "garbage collection aware", so it isn't safe to store pointers to other UObjects in an
* annotation unless external guarantees are made such that destruction of the other object removes the
* annotation.
* The advantage of FUObjectAnnotationChunked is that it can reclaim memory if subsets of UObjects within predefined chunks
* no longer have any annotations associated with them.
* @param TAnnotation type of the annotation
* @param bAutoRemove if true, annotation will automatically be removed, otherwise in non-final builds it will verify that the annotation was removed by other means prior to destruction.
**/
template <typename TAnnotation, bool bAutoRemove, int32 NumAnnotationsPerChunk = 64 * 1024>
class FUObjectAnnotationChunked: public FUObjectArray::FUObjectDeleteListener
{
struct TAnnotationChunk
{
int32 Num;
TAnnotation* Items;
TAnnotationChunk()
: Num(0), Items(nullptr)
{}
};
/** Master table to chunks of pointers **/
TArray<TAnnotationChunk> Chunks;
/** Number of elements we currently have **/
int32 NumAnnotations;
/** Number of elements we can have **/
int32 MaxAnnotations;
/** Current allocated memory */
uint32 CurrentAllocatedMemory;
/** Max allocated memory */
uint32 MaxAllocatedMemory;
/** Mutex */
FRWLock AnnotationArrayCritical;
/**
* Makes sure we have enough chunks to fit the new index
**/
void ExpandChunksToIndex(int32 Index) TSAN_SAFE
{
check(Index >= 0);
int32 ChunkIndex = Index / NumAnnotationsPerChunk;
if (ChunkIndex >= Chunks.Num())
{
Chunks.AddZeroed(ChunkIndex + 1 - Chunks.Num());
}
check(ChunkIndex < Chunks.Num());
MaxAnnotations = Chunks.Num() * NumAnnotationsPerChunk;
}
/**
* Initializes an annotation for the specified index, makes sure the chunk it resides in is allocated
**/
TAnnotation& AllocateAnnotation(int32 Index) TSAN_SAFE
{
ExpandChunksToIndex(Index);
const int32 ChunkIndex = Index / NumAnnotationsPerChunk;
const int32 WithinChunkIndex = Index % NumAnnotationsPerChunk;
TAnnotationChunk& Chunk = Chunks[ChunkIndex];
if (!Chunk.Items)
{
Chunk.Items = new TAnnotation[NumAnnotationsPerChunk];
CurrentAllocatedMemory += NumAnnotationsPerChunk * sizeof(TAnnotation);
MaxAllocatedMemory = FMath::Max(CurrentAllocatedMemory, MaxAllocatedMemory);
}
check(Chunk.Items[WithinChunkIndex].IsDefault());
Chunk.Num++;
check(Chunk.Num <= NumAnnotationsPerChunk);
NumAnnotations++;
return Chunk.Items[WithinChunkIndex];
}
/**
* Frees the annotation for the specified index
**/
void FreeAnnotation(int32 Index) TSAN_SAFE
{
const int32 ChunkIndex = Index / NumAnnotationsPerChunk;
const int32 WithinChunkIndex = Index % NumAnnotationsPerChunk;
TAnnotationChunk& Chunk = Chunks[ChunkIndex];
if (Chunk.Items != nullptr)
{
if (!Chunk.Items[WithinChunkIndex].IsDefault())
{
Chunk.Items[WithinChunkIndex] = TAnnotation();
Chunk.Num--;
check(Chunk.Num >= 0);
if (Chunk.Num == 0)
{
delete[] Chunk.Items;
Chunk.Items = nullptr;
const uint32 ChunkMemory = NumAnnotationsPerChunk * sizeof(TAnnotation);
check(CurrentAllocatedMemory >= ChunkMemory);
CurrentAllocatedMemory -= ChunkMemory;
}
NumAnnotations--;
}
check(NumAnnotations >= 0);
}
}
/**
* Releases all allocated memory and resets the annotation array
**/
void FreeAllAnnotations() TSAN_SAFE
{
for (TAnnotationChunk& Chunk: Chunks)
{
delete[] Chunk.Items;
}
Chunks.Empty();
NumAnnotations = 0;
MaxAnnotations = 0;
CurrentAllocatedMemory = 0;
MaxAllocatedMemory = 0;
}
/**
* Adds a new annotation for the specified index
**/
template <typename T>
void AddAnnotationInternal(int32 Index, T&& Annotation)
{
check(Index >= 0);
if (Annotation.IsDefault())
{
FreeAnnotation(Index); // adding the default annotation is the same as removing an annotation
}
else
{
if (NumAnnotations == 0 && Chunks.Num() == 0)
{
// we are adding the first one, so if we are auto removing or verifying removal, register now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.AddUObjectDeleteListener(this);
}
}
TAnnotation& NewAnnotation = AllocateAnnotation(Index);
NewAnnotation = Forward<T>(Annotation);
}
}
public:
/** Constructor : Probably not thread safe **/
FUObjectAnnotationChunked() TSAN_SAFE
: NumAnnotations(0)
, MaxAnnotations(0)
, CurrentAllocatedMemory(0)
, MaxAllocatedMemory(0)
{
}
virtual ~FUObjectAnnotationChunked()
{
RemoveAllAnnotations();
}
public:
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Object Object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(const UObjectBase* Object, const TAnnotation& Annotation)
{
check(Object);
AddAnnotation(GUObjectArray.ObjectToIndex(Object), Annotation);
}
void AddAnnotation(const UObjectBase* Object, TAnnotation&& Annotation)
{
check(Object);
AddAnnotation(GUObjectArray.ObjectToIndex(Object), MoveTemp(Annotation));
}
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Index Index of object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(int32 Index, const TAnnotation& Annotation)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
AddAnnotationInternal(Index, Annotation);
}
void AddAnnotation(int32 Index, TAnnotation&& Annotation)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
AddAnnotationInternal(Index, MoveTemp(Annotation));
}
TAnnotation& AddOrGetAnnotation(const UObjectBase* Object, TFunctionRef<TAnnotation()> NewAnnotationFn)
{
check(Object);
return AddOrGetAnnotation(GUObjectArray.ObjectToIndex(Object), NewAnnotationFn);
}
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Index Index of object to annotate.
* @param Annotation Annotation to associate with Object.
*/
TAnnotation& AddOrGetAnnotation(int32 Index, TFunctionRef<TAnnotation()> NewAnnotationFn)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
if (NumAnnotations == 0 && Chunks.Num() == 0)
{
// we are adding the first one, so if we are auto removing or verifying removal, register now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.AddUObjectDeleteListener(this);
}
}
ExpandChunksToIndex(Index);
const int32 ChunkIndex = Index / NumAnnotationsPerChunk;
const int32 WithinChunkIndex = Index % NumAnnotationsPerChunk;
TAnnotationChunk& Chunk = Chunks[ChunkIndex];
if (!Chunk.Items)
{
Chunk.Items = new TAnnotation[NumAnnotationsPerChunk];
CurrentAllocatedMemory += NumAnnotationsPerChunk * sizeof(TAnnotation);
MaxAllocatedMemory = FMath::Max(CurrentAllocatedMemory, MaxAllocatedMemory);
}
if (Chunk.Items[WithinChunkIndex].IsDefault())
{
Chunk.Num++;
check(Chunk.Num <= NumAnnotationsPerChunk);
NumAnnotations++;
Chunk.Items[WithinChunkIndex] = NewAnnotationFn();
check(!Chunk.Items[WithinChunkIndex].IsDefault());
}
return Chunk.Items[WithinChunkIndex];
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(const UObjectBase* Object)
{
check(Object);
RemoveAnnotation(GUObjectArray.ObjectToIndex(Object));
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(int32 Index)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
FreeAnnotation(Index);
}
/**
* Return the annotation associated with a uobject
*
* @param Object Object to return the annotation for
*/
FORCEINLINE TAnnotation GetAnnotation(const UObjectBase* Object)
{
check(Object);
return GetAnnotation(GUObjectArray.ObjectToIndex(Object));
}
/**
* Return the annotation associated with a uobject
*
* @param Index Index of the annotation to return
*/
FORCEINLINE TAnnotation GetAnnotation(int32 Index)
{
check(Index >= 0);
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_ReadOnly);
const int32 ChunkIndex = Index / NumAnnotationsPerChunk;
if (ChunkIndex < Chunks.Num())
{
const int32 WithinChunkIndex = Index % NumAnnotationsPerChunk;
TAnnotationChunk& Chunk = Chunks[ChunkIndex];
if (Chunk.Items != nullptr)
{
return Chunk.Items[WithinChunkIndex];
}
}
return TAnnotation();
}
/**
* Return the number of elements in the array
* Thread safe, but you know, someone might have added more elements before this even returns
* @return the number of elements in the array
**/
FORCEINLINE int32 GetAnnotationCount() const
{
return NumAnnotations;
}
/**
* Return the number max capacity of the array
* Thread safe, but you know, someone might have added more elements before this even returns
* @return the maximum number of elements in the array
**/
FORCEINLINE int32 GetMaxAnnottations() const TSAN_SAFE
{
return MaxAnnotations;
}
/**
* Return if this index is valid
* Thread safe, if it is valid now, it is valid forever. Other threads might be adding during this call.
* @param Index Index to test
* @return true, if this is a valid
**/
FORCEINLINE bool IsValidIndex(int32 Index) const
{
return Index >= 0 && Index < MaxAnnotations;
}
/**
* Removes all annotation from the annotation list.
*/
void RemoveAllAnnotations()
{
bool bHadAnnotations = (NumAnnotations > 0);
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
FreeAllAnnotations();
if (bHadAnnotations)
{
// we are removing the last one, so if we are auto removing or verifying removal, unregister now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
}
/**
* Frees chunk memory from empty chunks.
*/
void TrimAnnotations()
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
for (TAnnotationChunk& Chunk: Chunks)
{
if (Chunk.Num == 0 && Chunk.Items)
{
delete[] Chunk.Items;
Chunk.Items = nullptr;
const uint32 ChunkMemory = NumAnnotationsPerChunk * sizeof(TAnnotationChunk);
check(CurrentAllocatedMemory >= ChunkMemory);
CurrentAllocatedMemory -= ChunkMemory;
}
}
}
/** Returns the memory allocated by the internal array */
uint32 GetAllocatedSize() const
{
uint32 AllocatedSize = Chunks.GetAllocatedSize();
for (const TAnnotationChunk& Chunk: Chunks)
{
if (Chunk.Items)
{
AllocatedSize += NumAnnotationsPerChunk * sizeof(TAnnotation);
}
}
return AllocatedSize;
}
/** Returns the maximum memory allocated by the internal arrays */
uint32 GetMaxAllocatedSize() const
{
return Chunks.GetAllocatedSize() + MaxAllocatedMemory;
}
/**
* Interface for FUObjectAllocator::FUObjectDeleteListener
*
* @param Object object that has been destroyed
* @param Index index of object that is being deleted
*/
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (!bAutoRemove)
{
// in this case we are only verifying that the external assurances of removal are met
check(Index >= MaxAnnotations || GetAnnotation(Index).IsDefault());
}
else
#endif
{
RemoveAnnotation(Index);
}
}
virtual void OnUObjectArrayShutdown() override
{
RemoveAllAnnotations();
GUObjectArray.RemoveUObjectDeleteListener(this);
}
};
/**
* FUObjectAnnotationDense is a helper class that is used to store dense, fast, temporary, editor only, external
* or other tangential information about UObjects.
*
* There is a notion of a default annotation and UObjects default to this annotation.
*
* Annotations are automatically returned to the default when UObjects are destroyed.
* Annotation are not "garbage collection aware", so it isn't safe to store pointers to other UObjects in an
* annotation unless external guarantees are made such that destruction of the other object removes the
* annotation.
* @param TAnnotation type of the annotation
* @param bAutoRemove if true, annotation will automatically be removed, otherwise in non-final builds it will verify that the annotation was removed by other means prior to destruction.
**/
template <typename TAnnotation, bool bAutoRemove>
class FUObjectAnnotationDense: public FUObjectArray::FUObjectDeleteListener
{
public:
/**
* Interface for FUObjectAllocator::FUObjectDeleteListener
*
* @param Object object that has been destroyed
* @param Index index of object that is being deleted
*/
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (!bAutoRemove)
{
// in this case we are only verifying that the external assurances of removal are met
check(Index >= AnnotationArray.Num() || AnnotationArray[Index].IsDefault());
}
else
#endif
{
RemoveAnnotation(Index);
}
}
virtual void OnUObjectArrayShutdown() override
{
RemoveAllAnnotations();
GUObjectArray.RemoveUObjectDeleteListener(this);
}
/**
* Destructor, removes all annotations, which removes the annotation as a uobject destruction listener
*/
virtual ~FUObjectAnnotationDense()
{
RemoveAllAnnotations();
}
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Object Object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(const UObjectBase* Object, const TAnnotation& Annotation)
{
check(Object);
AddAnnotation(GUObjectArray.ObjectToIndex(Object), Annotation);
}
void AddAnnotation(const UObjectBase* Object, TAnnotation&& Annotation)
{
check(Object);
AddAnnotation(GUObjectArray.ObjectToIndex(Object), MoveTemp(Annotation));
}
/**
* Add an annotation to the annotation list. If the Annotation is the default, then the annotation is removed.
*
* @param Index Index of object to annotate.
* @param Annotation Annotation to associate with Object.
*/
void AddAnnotation(int32 Index, const TAnnotation& Annotation)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
AddAnnotationInternal(Index, Annotation);
}
void AddAnnotation(int32 Index, TAnnotation&& Annotation)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
AddAnnotationInternal(Index, MoveTemp(Annotation));
}
private:
template <typename T>
void AddAnnotationInternal(int32 Index, T&& Annotation)
{
check(Index >= 0);
if (Annotation.IsDefault())
{
RemoveAnnotationInternal(Index); // adding the default annotation is the same as removing an annotation
}
else
{
if (AnnotationArray.Num() == 0)
{
// we are adding the first one, so if we are auto removing or verifying removal, register now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.AddUObjectDeleteListener(this);
}
}
if (Index >= AnnotationArray.Num())
{
int32 AddNum = 1 + Index - AnnotationArray.Num();
int32 Start = AnnotationArray.AddUninitialized(AddNum);
while (AddNum--)
{
new (AnnotationArray.GetData() + Start++) TAnnotation();
}
}
AnnotationArray[Index] = Forward<T>(Annotation);
}
}
public:
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(const UObjectBase* Object)
{
check(Object);
RemoveAnnotation(GUObjectArray.ObjectToIndex(Object));
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(int32 Index)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
RemoveAnnotationInternal(Index);
}
private:
void RemoveAnnotationInternal(int32 Index)
{
check(Index >= 0);
if (Index < AnnotationArray.Num())
{
AnnotationArray[Index] = TAnnotation();
}
}
public:
/**
* Removes all annotation from the annotation list.
*
*/
void RemoveAllAnnotations()
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
bool bHadElements = (AnnotationArray.Num() > 0);
AnnotationArray.Empty();
if (bHadElements)
{
// we are removing the last one, so if we are auto removing or verifying removal, unregister now
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bAutoRemove)
#endif
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
}
/**
* Return the annotation associated with a uobject
*
* @param Object Object to return the annotation for
*/
FORCEINLINE TAnnotation GetAnnotation(const UObjectBase* Object)
{
check(Object);
return GetAnnotation(GUObjectArray.ObjectToIndex(Object));
}
/**
* Return the annotation associated with a uobject
*
* @param Index Index of the annotation to return
*/
FORCEINLINE TAnnotation GetAnnotation(int32 Index)
{
check(Index >= 0);
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_ReadOnly);
if (Index < AnnotationArray.Num())
{
return AnnotationArray[Index];
}
return TAnnotation();
}
/**
* Return the annotation associated with a uobject
*
* @param Object Object to return the annotation for
* @return Reference to annotation.
*/
FORCEINLINE TAnnotation& GetAnnotationRef(const UObjectBase* Object)
{
check(Object);
return GetAnnotationRef(GUObjectArray.ObjectToIndex(Object));
}
/**
* Return the annotation associated with a uobject. Adds one if the object has
* no annotation yet.
*
* @param Index Index of the annotation to return
* @return Reference to the annotation.
*/
FORCEINLINE TAnnotation& GetAnnotationRef(int32 Index)
{
FRWScopeLock AnnotationArrayLock(AnnotationArrayCritical, SLT_Write);
if (Index >= AnnotationArray.Num())
{
AddAnnotationInternal(Index, TAnnotation());
}
return AnnotationArray[Index];
}
/** Returns the memory allocated by the internal array */
uint32 GetAllocatedSize() const
{
return AnnotationArray.GetAllocatedSize();
}
private:
/**
* Map from live objects to an annotation
*/
TArray<TAnnotation> AnnotationArray;
FRWLock AnnotationArrayCritical;
};
/**
* FUObjectAnnotationDenseBool is custom annotation that tracks a bool per UObject.
**/
class FUObjectAnnotationDenseBool: public FUObjectArray::FUObjectDeleteListener
{
typedef uint32 TBitType;
enum
{
BitsPerElement = sizeof(TBitType) * 8
};
public:
/**
* Interface for FUObjectAllocator::FUObjectDeleteListener
*
* @param Object object that has been destroyed
* @param Index index of object that is being deleted
*/
virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index)
{
RemoveAnnotation(Index);
}
virtual void OnUObjectArrayShutdown() override
{
RemoveAllAnnotations();
GUObjectArray.RemoveUObjectDeleteListener(this);
}
/**
* Destructor, removes all annotations, which removes the annotation as a uobject destruction listener
*/
virtual ~FUObjectAnnotationDenseBool()
{
RemoveAllAnnotations();
}
/**
* Sets this bool annotation to true for this object
*
* @param Object Object to annotate.
*/
FORCEINLINE void Set(const UObjectBase* Object)
{
checkSlow(Object);
int32 Index = GUObjectArray.ObjectToIndex(Object);
checkSlow(Index >= 0);
if (AnnotationArray.Num() == 0)
{
GUObjectArray.AddUObjectDeleteListener(this);
}
if (Index >= AnnotationArray.Num() * BitsPerElement)
{
int32 AddNum = 1 + Index - AnnotationArray.Num() * BitsPerElement;
int32 AddElements = (AddNum + BitsPerElement - 1) / BitsPerElement;
checkSlow(AddElements);
AnnotationArray.AddZeroed(AddElements);
checkSlow(Index < AnnotationArray.Num() * BitsPerElement);
}
AnnotationArray[Index / BitsPerElement] |= TBitType(TBitType(1) << (Index % BitsPerElement));
}
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
FORCEINLINE void Clear(const UObjectBase* Object)
{
checkSlow(Object);
int32 Index = GUObjectArray.ObjectToIndex(Object);
RemoveAnnotation(Index);
}
/**
* Removes all bool annotations
*
*/
FORCEINLINE void ClearAll()
{
RemoveAllAnnotations();
}
/**
* Return the bool annotation associated with a uobject
*
* @param Object Object to return the bool annotation for
*/
FORCEINLINE bool Get(const UObjectBase* Object)
{
checkSlow(Object);
int32 Index = GUObjectArray.ObjectToIndex(Object);
checkSlow(Index >= 0);
if (Index < AnnotationArray.Num() * BitsPerElement)
{
return !!(AnnotationArray[Index / BitsPerElement] & TBitType(TBitType(1) << (Index % BitsPerElement)));
}
return false;
}
private:
/**
* Removes an annotation from the annotation list.
*
* @param Object Object to de-annotate.
*/
void RemoveAnnotation(int32 Index)
{
checkSlow(Index >= 0);
if (Index < AnnotationArray.Num() * BitsPerElement)
{
AnnotationArray[Index / BitsPerElement] &= ~TBitType(TBitType(1) << (Index % BitsPerElement));
}
}
/**
* Removes all annotation from the annotation list.
*
*/
void RemoveAllAnnotations()
{
bool bHadElements = (AnnotationArray.Num() > 0);
AnnotationArray.Empty();
if (bHadElements)
{
GUObjectArray.RemoveUObjectDeleteListener(this);
}
}
/**
* Map from live objects to an annotation
*/
TArray<TBitType> AnnotationArray;
};
// Definition is in UObjectGlobals.cpp
extern COREUOBJECT_API FUObjectAnnotationSparseBool GSelectedObjectAnnotation;