UE中的Object对象的回收不需要我们手动去delete,而是经过垃圾回收流程来销毁不再被引用的对象。GC使用的常用的Mark-Sweep算法(顺藤摸瓜)。一个对象Obj将在如下几种情况下被回收掉:
- Obj没有被标记为RootSet,并且没有被其它对象引用
- Obj在程序中被标记为RF_PendingKill(通过MarkPendingKill()接口)。
- Obj没有在void AddReferencedObjects( FReferenceCollector& Collector )中登记, 相关源码在
Engine\Source\Runtime\CoreUObject\Public\UObject\GCObject.h
。
一般地在UObject类体系的定义中,加入UPROPERTY标记的对象指针都会被GC在Mark-Sweep时进行考察引用,例如下面的代码片段:
/** Sound to play each time we fire */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
class USoundBase* FireSound;
/** AnimMontage to play each time we fire */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
class UAnimMontage* FireAnimation;
GC模块相关源码:
Engine\Source\Runtime\CoreUObject\Public\UObject\GarbageCollection.h
Engine\Source\Runtime\CoreUObject\Private\UObject\GarbageCollection.cpp
Engine\Source\Runtime\CoreUObject\Public\UObject\FastReferenceCollector.h
Engine\Source\Runtime\CoreUObject\Private\UObject\ReferenceChainSearch.cpp
ReferenceToken
在UObject体系中,每个类都有一个UClass实例用于描述该类的反射信息,使用UProperty来描述每个类成员变量,但是在GC中如果直接遍历UProperty来进行扫描对象引用的话,效率会比较低(因为有许多非Object引用型的Property),所以ReferenceToken就运用而生了,ReferenceToken是一组token流,描述了类中的对象引用情况。
/**
* Enum of different supported reference type tokens.
*/
enum EGCReferenceType
{
GCRT_None = 0,
GCRT_Object,
GCRT_PersistentObject,
GCRT_ArrayObject,
GCRT_ArrayStruct,
GCRT_FixedArray,
GCRT_AddStructReferencedObjects,
GCRT_AddReferencedObjects,
GCRT_AddTMapReferencedObjects,
GCRT_AddTSetReferencedObjects,
GCRT_EndOfPointer,
GCRT_EndOfStream,
};
/**
* Reference token stream class. Used for creating and parsing stream of object references.
*/
struct FGCReferenceTokenStream
{
/** Initialization value to ensure that we have the right skip index index */
enum EGCArrayInfoPlaceholder { E_GCSkipIndexPlaceholder = 0xDEADBABE };
/** Constructor */
FGCReferenceTokenStream()
{
check( sizeof(FGCReferenceInfo) == sizeof(uint32) );
}
/**
* Shrinks the token stream, removing array slack.
*/
void Shrink()
{
Tokens.Shrink();
}
/** Empties the token stream entirely */
void Empty()
{
Tokens.Empty();
}
/**
* Returns the size ofthe reference token stream.
*
* @returns Size of the stream.
*/
int32 Size() const
{
return Tokens.Num();
}
/**
* return true if this is empty
*/
bool IsEmpty() const
{
return Tokens.Num() == 0;
}
/**
* Prepends passed in stream to existing one.
*
* @param Other stream to concatenate
*/
void PrependStream( const FGCReferenceTokenStream& Other );
/**
* Emit reference info.
*
* @param ReferenceInfo reference info to emit.
*
* @return Index of the reference info in the token stream.
*/
int32 EmitReferenceInfo(FGCReferenceInfo ReferenceInfo);
/**
* Emit placeholder for aray skip index, updated in UpdateSkipIndexPlaceholder
*
* @return the index of the skip index, used later in UpdateSkipIndexPlaceholder
*/
uint32 EmitSkipIndexPlaceholder();
/**
* Updates skip index place holder stored and passed in skip index index with passed
* in skip index. The skip index is used to skip over tokens in the case of an emtpy
* dynamic array.
*
* @param SkipIndexIndex index where skip index is stored at.
* @param SkipIndex index to store at skip index index
*/
void UpdateSkipIndexPlaceholder( uint32 SkipIndexIndex, uint32 SkipIndex );
/**
* Emit count
*
* @param Count count to emit
*/
void EmitCount( uint32 Count );
/**
* Emit a pointer
*
* @param Ptr pointer to emit
*/
void EmitPointer( void const* Ptr );
/**
* Emit stride
*
* @param Stride stride to emit
*/
void EmitStride( uint32 Stride );
/**
* Increase return count on last token.
*
* @return index of next token
*/
uint32 EmitReturn();
/**
* Helper function to quickly replace or add ARO call.
*/
void ReplaceOrAddAddReferencedObjectsCall(void (*AddReferencedObjectsPtr)(UObject*, class FReferenceCollector&));
/**
* Reads count and advances stream.
*
* @return read in count
*/
FORCEINLINE uint32 ReadCount( uint32& CurrentIndex )
{
return Tokens[CurrentIndex++];
}
/**
* Reads stride and advances stream.
*
* @return read in stride
*/
FORCEINLINE uint32 ReadStride( uint32& CurrentIndex )
{
return Tokens[CurrentIndex++];
}
/**
* Reads pointer and advance stream
*
* @return read in pointer
*/
FORCEINLINE void* ReadPointer( uint32& CurrentIndex )
{
UPTRINT Result = UPTRINT(Tokens[CurrentIndex++]);
#if PLATFORM_64BITS // warning C4293: '<<' : shift count negative or too big, undefined behavior, so we needed the ifdef
static_assert(sizeof(void*) == 8, "Pointer size mismatch.");
Result |= UPTRINT(Tokens[CurrentIndex++]) << 32;
#else
static_assert(sizeof(void*) == 4, "Pointer size mismatch.");
#endif
return (void*)Result;
}
/**
* Reads in reference info and advances stream.
*
* @return read in reference info
*/
FORCEINLINE FGCReferenceInfo ReadReferenceInfo( uint32& CurrentIndex )
{
return Tokens[CurrentIndex++];
}
/**
* Access reference info at passed in index. Used as helper to eliminate LHS.
*
* @return Reference info at passed in index
*/
FORCEINLINE FGCReferenceInfo AccessReferenceInfo( uint32 CurrentIndex ) const
{
return Tokens[CurrentIndex];
}
/**
* Read in skip index and advances stream.
*
* @return read in skip index
*/
FORCEINLINE FGCSkipInfo ReadSkipInfo( uint32& CurrentIndex )
{
FGCSkipInfo SkipInfo = Tokens[CurrentIndex];
SkipInfo.SkipIndex += CurrentIndex;
CurrentIndex++;
return SkipInfo;
}
/**
* Read return count stored at the index before the skip index. This is required
* to correctly return the right amount of levels when skipping over an empty array.
*
* @param SkipIndex index of first token after array
*/
FORCEINLINE uint32 GetSkipReturnCount( FGCSkipInfo SkipInfo )
{
check( SkipInfo.SkipIndex > 0 && SkipInfo.SkipIndex <= (uint32)Tokens.Num() );
FGCReferenceInfo ReferenceInfo = Tokens[SkipInfo.SkipIndex-1];
check( ReferenceInfo.Type != GCRT_None );
return ReferenceInfo.ReturnCount - SkipInfo.InnerReturnCount;
}
/**
* Queries the stream for an end of stream condition
*
* @return true if the end of the stream has been reached, false otherwise
*/
FORCEINLINE bool EndOfStream( uint32 CurrentIndex )
{
return CurrentIndex >= (uint32)Tokens.Num();
}
private:
/**
* Helper function to store a pointer into a preallocated token stream.
*
* @param Stream Preallocated token stream.
* @param Ptr pointer to store
*/
FORCEINLINE void StorePointer( uint32* Stream, void const* Ptr )
{
#if PLATFORM_64BITS // warning C4293: '<<' : shift count negative or too big, undefined behavior, so we needed the ifdef
static_assert(sizeof(void*) == 8, "Pointer size mismatch.");
Stream[0] = UPTRINT(Ptr) & 0xffffffff;
Stream[1] = UPTRINT(Ptr) >> 32;
#else
static_assert(sizeof(void*) == 4, "Pointer size mismatch.");
Stream[0] = PTRINT(Ptr);
#endif
}
/** Token array */
TArray<uint32> Tokens;
};
TokenStream的生成
void UClass::AssembleReferenceTokenStream(bool bForce)
{
// Lock for non-native classes
struct FScopeLockIfNotNative
{
FCriticalSection& ScopeCritical;
const bool bNotNative;
FScopeLockIfNotNative(FCriticalSection& InScopeCritical, bool bIsNotNative)
: ScopeCritical(InScopeCritical)
, bNotNative(bIsNotNative)
{
if (bNotNative)
{
ScopeCritical.Lock();
}
}
~FScopeLockIfNotNative()
{
if (bNotNative)
{
ScopeCritical.Unlock();
}
}
} ReferenceTokenStreamLock(ReferenceTokenStreamCritical, !(ClassFlags & CLASS_Native));
UE_CLOG(!IsInGameThread() && !IsGarbageCollectionLocked(), LogGarbage, Fatal, TEXT("AssembleReferenceTokenStream for %s called on a non-game thread while GC is not locked."), *GetFullName());
if (!HasAnyClassFlags(CLASS_TokenStreamAssembled) || bForce)
{
if (bForce)
{
ReferenceTokenStream.Empty();
#if !(UE_BUILD_TEST || UE_BUILD_SHIPPING)
DebugTokenMap.Empty();
#endif
ClassFlags &= ~CLASS_TokenStreamAssembled;
}
TArray<const UStructProperty*> EncounteredStructProps;
// Iterate over properties defined in this class
for( TFieldIterator<UProperty> It(this,EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
UProperty* Property = *It;
Property->EmitReferenceInfo(*this, 0, EncounteredStructProps); // 每个数UProperty负责指定Token
}
if (GetSuperClass())
{
// Make sure super class has valid token stream.
GetSuperClass()->AssembleReferenceTokenStream();
if (!GetSuperClass()->ReferenceTokenStream.IsEmpty())
{
// Prepend super's stream. This automatically handles removing the EOS token.
PrependStreamWithSuperClass(*GetSuperClass()); // 在前面添加父类的TokenStream
}
}
else
{
UObjectBase::EmitBaseReferences(this);
}
#if !WITH_EDITOR
// In no-editor builds UObject::ARO is empty, thus only classes
// which implement their own ARO function need to have the ARO token generated.
if (ClassAddReferencedObjects != &UObject::AddReferencedObjects)
#endif
{
check(ClassAddReferencedObjects != NULL);
ReferenceTokenStream.ReplaceOrAddAddReferencedObjectsCall(ClassAddReferencedObjects);
}
if (ReferenceTokenStream.IsEmpty())
{
return;
}
// Emit end of stream token.
static const FName EOSDebugName("EOS");
EmitObjectReference(0, EOSDebugName, GCRT_EndOfStream);
// Shrink reference token stream to proper size.
ReferenceTokenStream.Shrink();
check(!HasAnyClassFlags(CLASS_TokenStreamAssembled)); // recursion here is probably bad
ClassFlags |= CLASS_TokenStreamAssembled;
}
}
AssembleReferenceTokenStreams的调用情形一:
CollectGarbage的执行
通过调用GC模块提供的void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge);
接口进行垃圾回收。在标记阶段是一次性标记完的,但是在清除阶段可以采用增量式清除(也就是一次调用里不必销毁掉所有垃圾对象)
/**
* Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set
*
* @param KeepFlags objects with those flags will be kept regardless of being referenced or not
* @param bPerformFullPurge if true, perform a full purge after the mark pass
*/
void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)
IncrementalPurgeGarbage
FRealtimeGC
负责Object是否被直接或间接引用分析。
在TFastReferenceCollector::CollectReferences(FGCArrayStruct& ArrayStruct)中:
ProcessObjectArray()函数进行了TokenStream解析和引用标记
/**
* Traverses UObject token stream to find existing references
*
* @param InObjectsToSerializeArray Objects to process
* @param MyCompletionGraphEvent Task graph event
*/
void ProcessObjectArray(FGCArrayStruct& InObjectsToSerializeStruct, const FGraphEventRef& MyCompletionGraphEvent)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("TFastReferenceCollector::ProcessObjectArray"), STAT_FFastReferenceCollector_ProcessObjectArray, STATGROUP_GC);
UObject* CurrentObject = nullptr;
const int32 MinDesiredObjectsPerSubTask = ReferenceProcessor.GetMinDesiredObjectsPerSubTask(); // sometimes there will be less, a lot less
/** Growing array of objects that require serialization */
FGCArrayStruct& NewObjectsToSerializeStruct = *ArrayPool.GetArrayStructFromPool();
// Ping-pong between these two arrays if there's not enough objects to spawn a new task
TArray<UObject*>& ObjectsToSerialize = InObjectsToSerializeStruct.ObjectsToSerialize;
TArray<UObject*>& NewObjectsToSerialize = NewObjectsToSerializeStruct.ObjectsToSerialize;
// Presized "recursion" stack for handling arrays and structs.
TArray<FStackEntry> Stack;
Stack.AddUninitialized(128); //@todo rtgc: need to add code handling more than 128 layers of recursion or at least assert
// it is necessary to have at least one extra item in the array memory block for the iffy prefetch code, below
ObjectsToSerialize.Reserve(ObjectsToSerialize.Num() + 1);
// Keep serializing objects till we reach the end of the growing array at which point
// we are done.
int32 CurrentIndex = 0;
do
{
CollectorType ReferenceCollector(ReferenceProcessor, NewObjectsToSerializeStruct);
while (CurrentIndex < ObjectsToSerialize.Num()) // 逐个对象遍历
{
#if PERF_DETAILED_PER_CLASS_GC_STATS
uint32 StartCycles = FPlatformTime::Cycles();
#endif
CurrentObject = ObjectsToSerialize[CurrentIndex++]; // 当前对象
// GetData() used to avoiding bounds checking (min and max)
// FMath::Min used to avoid out of bounds (without branching) on last iteration. Though anything can be passed into PrefetchBlock,
// reading ObjectsToSerialize out of bounds is not safe since ObjectsToSerialize[Num()] may be an unallocated/unsafe address.
const UObject * const NextObject = ObjectsToSerialize.GetData()[FMath::Min<int32>(CurrentIndex, ObjectsToSerialize.Num() - 1)];
// Prefetch the next object assuming that the property size of the next object is the same as the current one.
// This allows us to avoid a branch here.
FPlatformMisc::PrefetchBlock(NextObject, CurrentObject->GetClass()->GetPropertiesSize());
//@todo rtgc: we need to handle object references in struct defaults
// Make sure that token stream has been assembled at this point as the below code relies on it.
if (!bParallel && bAutoGenerateTokenStream)
{
UClass* ObjectClass = CurrentObject->GetClass();
if (!ObjectClass->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
ObjectClass->AssembleReferenceTokenStream();
}
}
#if DO_CHECK
if (!CurrentObject->GetClass()->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
UE_LOG(LogGarbage, Fatal, TEXT("%s does not yet have a token stream assembled."), *GetFullNameSafe(CurrentObject->GetClass()));
}
#endif
// Get pointer to token stream and jump to the start.
FGCReferenceTokenStream* RESTRICT TokenStream = &CurrentObject->GetClass()->ReferenceTokenStream;
uint32 TokenStreamIndex = 0;
// Keep track of index to reference info. Used to avoid LHSs.
uint32 ReferenceTokenStreamIndex = 0;
// Create stack entry and initialize sane values.
FStackEntry* RESTRICT StackEntry = Stack.GetData();
uint8* StackEntryData = (uint8*)CurrentObject;
StackEntry->Data = StackEntryData; // 当前对象内存地址
StackEntry->Stride = 0;
StackEntry->Count = -1;
StackEntry->LoopStartIndex = -1;
// Keep track of token return count in separate integer as arrays need to fiddle with it.
int32 TokenReturnCount = 0;
// Parse the token stream.
while (true)
{
// Cache current token index as it is the one pointing to the reference info.
ReferenceTokenStreamIndex = TokenStreamIndex;
// Handle returning from an array of structs, array of structs of arrays of ... (yadda yadda)
// TokenRenturnCount表示了array嵌套层数
for (int32 ReturnCount = 0; ReturnCount<TokenReturnCount; ReturnCount++)
{
// Make sure there's no stack underflow.
check(StackEntry->Count != -1);
// We pre-decrement as we're already through the loop once at this point.
if (--StackEntry->Count > 0)
{
// Point data to next entry.
StackEntryData = StackEntry->Data + StackEntry->Stride; // 数组元素位置(迭代前进)
StackEntry->Data = StackEntryData;
// Jump back to the beginning of the loop.
TokenStreamIndex = StackEntry->LoopStartIndex; // 定位到当前数据结构的token位置
ReferenceTokenStreamIndex = StackEntry->LoopStartIndex;
// We're not done with this token loop so we need to early out instead of backing out further.
break;
}
else
{
StackEntry--;
StackEntryData = StackEntry->Data;
}
}
TokenStreamIndex++;
FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex); // 读取token
switch(ReferenceInfo.Type)
{
case GCRT_Object:
{
// We're dealing with an object reference.
UObject** ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset); // 成员变量的地址
UObject*& Object = *ObjectPtr;
TokenReturnCount = ReferenceInfo.ReturnCount;
ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, Object, ReferenceTokenStreamIndex, true);
}
break;
case GCRT_ArrayObject:
{
// We're dealing with an array of object references.
TArray<UObject*>& ObjectArray = *((TArray<UObject*>*)(StackEntryData + ReferenceInfo.Offset));
TokenReturnCount = ReferenceInfo.ReturnCount;
for (int32 ObjectIndex = 0, ObjectNum = ObjectArray.Num(); ObjectIndex < ObjectNum; ++ObjectIndex)
{
ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, ObjectArray[ObjectIndex], ReferenceTokenStreamIndex, true);
}
}
break;
case GCRT_ArrayStruct:
{
// We're dealing with a dynamic array of structs.
const FScriptArray& Array = *((FScriptArray*)(StackEntryData + ReferenceInfo.Offset));
StackEntry++; // 分配一个栈slot,用于深入数组元素
StackEntryData = (uint8*)Array.GetData();
StackEntry->Data = StackEntryData;
StackEntry->Stride = TokenStream->ReadStride(TokenStreamIndex);
StackEntry->Count = Array.Num();
const FGCSkipInfo SkipInfo = TokenStream->ReadSkipInfo(TokenStreamIndex);
StackEntry->LoopStartIndex = TokenStreamIndex;
if (StackEntry->Count == 0)
{
// Skip empty array by jumping to skip index and set return count to the one about to be read in.
TokenStreamIndex = SkipInfo.SkipIndex;
TokenReturnCount = TokenStream->GetSkipReturnCount(SkipInfo);
}
else
{
// Loop again.
check(StackEntry->Data);
TokenReturnCount = 0;
}
}
break;
case GCRT_PersistentObject:
{
// We're dealing with an object reference.
UObject** ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset);
UObject*& Object = *ObjectPtr;
TokenReturnCount = ReferenceInfo.ReturnCount;
ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, Object, ReferenceTokenStreamIndex, false);
}
break;
case GCRT_FixedArray:
{
// We're dealing with a fixed size array
uint8* PreviousData = StackEntryData;
StackEntry++;
StackEntryData = PreviousData;
StackEntry->Data = PreviousData;
StackEntry->Stride = TokenStream->ReadStride(TokenStreamIndex);
StackEntry->Count = TokenStream->ReadCount(TokenStreamIndex);
StackEntry->LoopStartIndex = TokenStreamIndex;
TokenReturnCount = 0;
}
break;
case GCRT_AddStructReferencedObjects:
{
// We're dealing with a function call
void const* StructPtr = (void*)(StackEntryData + ReferenceInfo.Offset);
TokenReturnCount = ReferenceInfo.ReturnCount;
UScriptStruct::ICppStructOps::TPointerToAddStructReferencedObjects Func = (UScriptStruct::ICppStructOps::TPointerToAddStructReferencedObjects) TokenStream->ReadPointer(TokenStreamIndex);
Func(StructPtr, ReferenceCollector);
}
break;
case GCRT_AddReferencedObjects:
{
// Static AddReferencedObjects function call.
void(*AddReferencedObjects)(UObject*, FReferenceCollector&) = (void(*)(UObject*, FReferenceCollector&))TokenStream->ReadPointer(TokenStreamIndex);
TokenReturnCount = ReferenceInfo.ReturnCount;
AddReferencedObjects(CurrentObject, ReferenceCollector);
}
break;
case GCRT_AddTMapReferencedObjects:
{
void* Map = StackEntryData + ReferenceInfo.Offset;
UMapProperty* MapProperty = (UMapProperty*)TokenStream->ReadPointer(TokenStreamIndex);
TokenReturnCount = ReferenceInfo.ReturnCount;
FSimpleObjectReferenceCollectorArchive CollectorArchive(CurrentObject, ReferenceCollector);
MapProperty->SerializeItem(CollectorArchive, Map, nullptr);
}
break;
case GCRT_AddTSetReferencedObjects:
{
void* Set = StackEntryData + ReferenceInfo.Offset;
USetProperty* SetProperty = (USetProperty*)TokenStream->ReadPointer(TokenStreamIndex);
TokenReturnCount = ReferenceInfo.ReturnCount;
FSimpleObjectReferenceCollectorArchive CollectorArchive(CurrentObject, ReferenceCollector);
SetProperty->SerializeItem(CollectorArchive, Set, nullptr);
}
break;
case GCRT_EndOfPointer:
{
TokenReturnCount = ReferenceInfo.ReturnCount;
}
break;
case GCRT_EndOfStream:
{
// Break out of loop.
goto EndLoop;
}
break;
default:
{
UE_LOG(LogGarbage, Fatal, TEXT("Unknown token"));
break;
}
}
}
EndLoop:
check(StackEntry == Stack.GetData());
if (bParallel && NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
{
// This will start queueing task with objects from the end of array until there's less objects than worth to queue
const int32 ObjectsPerSubTask = FMath::Max<int32>(MinDesiredObjectsPerSubTask, NewObjectsToSerialize.Num() / FTaskGraphInterface::Get().GetNumWorkerThreads());
while (NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
{
const int32 StartIndex = FMath::Max(0, NewObjectsToSerialize.Num() - ObjectsPerSubTask);
const int32 NumThisTask = NewObjectsToSerialize.Num() - StartIndex;
if (MyCompletionGraphEvent.GetReference())
{
MyCompletionGraphEvent->DontCompleteUntil(TGraphTask< FCollectorTask >::CreateTask().ConstructAndDispatchWhenReady(this, &NewObjectsToSerialize, StartIndex, NumThisTask, ArrayPool));
}
else
{
TaskQueue.AddTask(&NewObjectsToSerialize, StartIndex, NumThisTask);
}
NewObjectsToSerialize.SetNumUnsafeInternal(StartIndex);
}
}
#if PERF_DETAILED_PER_CLASS_GC_STATS
// Detailed per class stats should not be performed when parallel GC is running
check(!bParallel);
ReferenceProcessor.UpdateDetailedStats(CurrentObject, FPlatformTime::Cycles() - StartCycles);
#endif
}
if (bParallel && NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
{
const int32 ObjectsPerSubTask = FMath::Max<int32>(MinDesiredObjectsPerSubTask, NewObjectsToSerialize.Num() / FTaskGraphInterface::Get().GetNumWorkerThreads());
int32 StartIndex = 0;
while (StartIndex < NewObjectsToSerialize.Num())
{
const int32 NumThisTask = FMath::Min<int32>(ObjectsPerSubTask, NewObjectsToSerialize.Num() - StartIndex);
if (MyCompletionGraphEvent.GetReference())
{
MyCompletionGraphEvent->DontCompleteUntil(TGraphTask< FCollectorTask >::CreateTask().ConstructAndDispatchWhenReady(this, &NewObjectsToSerialize, StartIndex, NumThisTask, ArrayPool));
}
else
{
TaskQueue.AddTask(&NewObjectsToSerialize, StartIndex, NumThisTask);
}
StartIndex += NumThisTask;
}
NewObjectsToSerialize.SetNumUnsafeInternal(0);
}
else if (NewObjectsToSerialize.Num())
{
// Don't spawn a new task, continue in the current one
// To avoid allocating and moving memory around swap ObjectsToSerialize and NewObjectsToSerialize arrays
Exchange(ObjectsToSerialize, NewObjectsToSerialize);
// Empty but don't free allocated memory
NewObjectsToSerialize.SetNumUnsafeInternal(0);
CurrentIndex = 0;
}
}
while (CurrentIndex < ObjectsToSerialize.Num());
#if PERF_DETAILED_PER_CLASS_GC_STATS
// Detailed per class stats should not be performed when parallel GC is running
check(!bParallel);
ReferenceProcessor.LogDetailedStatsSummary();
#endif
ArrayPool.ReturnToPool(&NewObjectsToSerializeStruct);
}
注意在标记ObjectReferecen时:
ReferenceToken案例
USTRUCT()
struct FIRSTPERSONCPP_API FRoundHero
{
GENERATED_BODY()
UPROPERTY()
TArray<UObject*> Skills;
UPROPERTY()
UObject* Weapon[2];
};
USTRUCT()
struct FIRSTPERSONCPP_API FRoundInfo
{
GENERATED_BODY()
int32 Index;
float TimeLength;
UPROPERTY()
TArray<FRoundHero> Heros;
UPROPERTY()
UObject* RecordData;
};
/**
*
*/
UCLASS()
class FIRSTPERSONCPP_API UPlayerData : public UObject
{
GENERATED_BODY()
public:
UPlayerData();
FString PlayerName;
uint32_t BulletNum;
UPROPERTY()
AActor *pHero[4];
UPROPERTY()
TArray<UObject*> HelperData;
UPROPERTY()
TArray<FRoundInfo> Rounds;
};
Token序列如下(这里忽略基类的token):
// 发射步骤和token stream如下:
1. 字段 AActor *pHero[4]
EmitObjectReference 0 {GCRT_FixedArray, Offset}
EmitStride 1 {Element Size }
EmitCount 2 {Count=4}
EmitObjectReference 3 {GCRT_Object, Offset, ReturnCount=0}
EmitReturn 修改Token 3的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}
2. 字段 TArray<UObject*> HelperData
EmitObjectReference 4 {GCRT_ArrayObject, Offset}
3. 字段 TArray<FRoundInfo> Rounds
EmitObjectReference 5 {GCRT_ArrayStruct, Offset}
EmitStride 6 {Element Size}
EmitSkipIndexPlaceholder 7 {E_GCSkipIndexPlaceholder}
/////////////{{
UStructProperty::EmitReferenceInfo, 结构FRoundInfo
3.1 字段 TArray<FRoundHero> Heros
EmitObjectReference 8 {GCRT_ArrayStruct, Offset}
EmitStride 9 {Element Size}
EmitSkipIndexPlaceholder 10 {E_GCSkipIndexPlaceholder}
////////////{{
UStructProperty::EmitReferenceInfo, 结构FRoundHero
3.1.1 字段 TArray<UObject*> Skills
EmitObjectReference 11 {GCRT_ArrayObject, Offset}
3.1.2 字段 UObject* Weapon
EmitObjectReference 12 {GCRT_FixedArray, Offset}
EmitStride 13 {Element Size}
EmitCount 14 {Count=2}
EmitObjectReference 15 {GCRT_Object, Offset, ReturnCount=0}
EmitReturn 修改Token 15的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}
////////////}}
EmitReturn 修改Token 15的ReturnCount, => {GCRT_Object, Offset, ReturnCount=2}
UpdateSkipIndexPlaceholder 修改Token 10的FGCSkipInfo => {SkipIndex=6, InnerReturnCount=1}
3.2 字段 UObject* RecordData
EmitObjectReference 16 {GCRT_Object, Offset, ReturnCount=0}
///////////////}}
EmitReturn 修改Token 16的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}
UpdateSkipIndexPlaceholder 修改Token 7的FGCSkipInfo => {SkipIndex=10, InnerReturnCount=0}
4. End Of Token
EmitObjectReference 17 {GCRT_EndOfStream, 0}
关于GC_Cluster
这个尚未研究,个人估计是为了优化垃圾标记,比如在粒子系统中,这些例子对象标记为一组Cluster,那么可以一次性标记可见性。