背后的原理
先看FActorIterator的定义
c++
/**
* Actor iterator
* Note that when Playing In Editor, this will find actors only in CurrentWorld
*/
class FActorIterator : public TActorIteratorBase<FActorIterator>
{
//.....
}
找到基类TActorIteratorBase
c++
/**
* Template class used to filter actors by certain characteristics
*/
template <typename Derived>
class TActorIteratorBase
{
public:
/**
* Iterates to next suitable actor.
*/
void operator++()
{
// Use local version to avoid LHSs as compiler is not required to write out member variables to memory.
AActor* LocalCurrentActor = nullptr;
int32 LocalIndex = State->Index;
TArray<UObject*>& LocalObjectArray = State->ObjectArray;
TArray<AActor*>& LocalSpawnedActorArray = State->SpawnedActorArray;
const UWorld* LocalCurrentWorld = State->CurrentWorld;
while(++LocalIndex < (LocalObjectArray.Num() + LocalSpawnedActorArray.Num()))
{
if (LocalIndex < LocalObjectArray.Num())
{
LocalCurrentActor = static_cast<AActor*>(LocalObjectArray[LocalIndex]);
}
else
{
LocalCurrentActor = LocalSpawnedActorArray[LocalIndex - LocalObjectArray.Num()];
}
State->ConsideredCount++;
ULevel* ActorLevel = LocalCurrentActor ? LocalCurrentActor->GetLevel() : nullptr;
if ( ActorLevel
&& static_cast<const Derived*>(this)->IsActorSuitable(LocalCurrentActor)
&& static_cast<const Derived*>(this)->CanIterateLevel(ActorLevel)
&& ActorLevel->GetWorld() == LocalCurrentWorld)
{
// ignore non-persistent world settings
if (ActorLevel == LocalCurrentWorld->PersistentLevel || !LocalCurrentActor->IsA(AWorldSettings::StaticClass()))
{
State->CurrentActor = LocalCurrentActor;
State->Index = LocalIndex;
return;
}
}
}
State->CurrentActor = nullptr;
State->ReachedEnd = true;
}
//.....省略剩余代码
}
可以看到TActorIteratorBase基本就是在遍历FActorIteratorState里的两个数组。为啥有两个数组?因为遍历的过程中可能又Spawn了新的Actor。
接着看FActorIteratorState的代码
c++
/*-----------------------------------------------------------------------------
Iterator for the editor that loops through all selected actors.
-----------------------------------------------------------------------------*/
/**
* Abstract base class for actor iteration. Implements all operators and relies on IsActorSuitable
* to be overridden by derived class.
* Note that when Playing In Editor, this will find actors only in CurrentWorld.
*/
class FActorIteratorState
{
public:
/** Current world we are iterating upon */
const UWorld* CurrentWorld;
/** Results from the GetObjectsOfClass query */
TArray<UObject*> ObjectArray;
/** index of the current element in the object array */
int32 Index;
/** Whether we already reached the end */
bool ReachedEnd;
/** Number of actors that have been considered thus far */
int32 ConsideredCount;
/** Current actor pointed to by actor iterator */
AActor* CurrentActor;
/** Contains any actors spawned during iteration */
TArray<AActor*> SpawnedActorArray;
/** The class type we are iterating, kept for filtering */
UClass* DesiredClass;
/** Handle to the registered OnActorSpawned delegate */
FDelegateHandle ActorSpawnedDelegateHandle;
/**
* Default ctor, inits everything
*/
FActorIteratorState(const UWorld* InWorld, const TSubclassOf<AActor> InClass) :
CurrentWorld( InWorld ),
Index( -1 ),
ReachedEnd( false ),
ConsideredCount( 0 ),
CurrentActor(nullptr),
DesiredClass(InClass)
{
check(IsInGameThread());
check(CurrentWorld);
#if WITH_EDITOR
// In the editor, you are more likely to have many worlds in memory at once.
// As an optimization to avoid iterating over many actors that are not in the world we are asking for,
// if the filter class is AActor, just use the actors that are in the world you asked for.
// This could be useful in runtime code as well if there are many worlds in memory, but for now we will leave
// it in editor code.
if (InClass == AActor::StaticClass())
{
// First determine the number of actors in the world to reduce reallocations when we append them to the array below.
int32 NumActors = 0;
for (ULevel* Level : InWorld->GetLevels())
{
if (Level)
{
NumActors += Level->Actors.Num();
}
}
// Presize the array
ObjectArray.Reserve(NumActors);
// Fill the array
for (ULevel* Level : InWorld->GetLevels())
{
if (Level)
{
ObjectArray.Append(Level->Actors);
}
}
}
else
#endif // WITH_EDITOR
{
constexpr EObjectFlags ExcludeFlags = RF_ClassDefaultObject;
GetObjectsOfClass(InClass, ObjectArray, true, ExcludeFlags, EInternalObjectFlags::Garbage);
}
const auto ActorSpawnedDelegate = FOnActorSpawned::FDelegate::CreateRaw(this, &FActorIteratorState::OnActorSpawned);
ActorSpawnedDelegateHandle = CurrentWorld->AddOnActorSpawnedHandler(ActorSpawnedDelegate);
}
~FActorIteratorState()
{
CurrentWorld->RemoveOnActorSpawnedHandler(ActorSpawnedDelegateHandle);
}
/**
* Returns the current suitable actor pointed at by the Iterator
*
* @return Current suitable actor
*/
FORCEINLINE AActor* GetActorChecked() const
{
check(CurrentActor);
checkf(!CurrentActor->IsUnreachable(), TEXT("%s"), *CurrentActor->GetFullName());
return CurrentActor;
}
private:
void OnActorSpawned(AActor* InActor)
{
if (InActor->IsA(DesiredClass))
{
SpawnedActorArray.AddUnique(InActor);
}
}
};
着重看红色框中的代码
可以看到在Editor下当InClass是AActor::StaticClass()的时候,会遍历当前World中所有的Actor。
再看FActorIterator的构造函数
可以看到FActorIterator只传一个构造参数的时候,InClass会默认是AActor::StaticClass(),也就会遍历场景中所有的Actor。当我们明确知道自己想要遍历的是Actor子类时,却因为少传了一个参数而被迫轮询了一遍所有Actor!
再来看ForEachObjectOfClass的代码。
c++
void ForEachObjectOfClass(const UClass* ClassToLookFor, TFunctionRef<void(UObject*)> Operation, bool bIncludeDerivedClasses, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
TRACE_CPUPROFILER_EVENT_SCOPE(ForEachObjectOfClass);
// Most classes searched for have around 10 subclasses, some have hundreds
TArray<const UClass*, TInlineAllocator<16>> ClassesToSearch;
ClassesToSearch.Add(ClassToLookFor);
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
if (bIncludeDerivedClasses)
{
RecursivelyPopulateDerivedClasses(ThreadHash, ClassToLookFor, ClassesToSearch);
}
ForEachObjectOfClasses_Implementation(ThreadHash, ClassesToSearch, Operation, ExclusionFlags, ExclusionInternalFlags);
}
RecursivelyPopulateDerivedClasses是查找ClassToLookFor所有的子类。
ForEachObjectOfClasses_Implementation则是在遍历所有这些子类的实例对象。代码如下:
c++
FORCEINLINE void ForEachObjectOfClasses_Implementation(FUObjectHashTables& ThreadHash, TArrayView<const UClass* const> ClassesToLookFor, TFunctionRef<void(UObject*)> Operation, EObjectFlags ExcludeFlags /*= RF_ClassDefaultObject*/, EInternalObjectFlags ExclusionInternalFlags /*= EInternalObjectFlags::None*/)
{
TRACE_CPUPROFILER_EVENT_SCOPE(ForEachObjectOfClasses_Implementation);
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
ExclusionInternalFlags |= UE::GC::GUnreachableObjectFlag;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags |= EInternalObjectFlags::AsyncLoading;
}
TBucketMapLock ClassToObjectListMapLock(ThreadHash.ClassToObjectListMap);
for (const UClass* SearchClass : ClassesToLookFor)
{
auto List = ThreadHash.ClassToObjectListMap.Find(SearchClass);
if (List)
{
for (auto ObjectIt = List->CreateIterator(); ObjectIt; ++ObjectIt)
{
UObject* Object = static_cast<UObject*>(*ObjectIt);
if (!Object->HasAnyFlags(ExcludeFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
if (UE::GC::GIsIncrementalReachabilityPending)
{
UE::GC::MarkAsReachable(Object);
}
Operation(Object);
}
}
}
}
}
解决方法及建议
- 对于Actor子类的遍历
建议使用TActorIterator。示例如下:
c++
for (TActorIterator<AInstancedFoliageActor> It(InWorld); It; ++It)
{
AInstancedFoliageActor* IFA = *It;
if (IFA->GetLevel() == InLevel)
{
IFA->PostApplyLevelOffset(InOffset, bWorldShift);
}
}
- 对于UActorComponent或UObject子类
建议使用ForEachObjectOfClass或者GetObjectsOfClass。示例如下:
c++
TArray<UWorld*, TInlineAllocator<4>> WorldsToEOFUpdate;
ForEachObjectOfClass(UWorld::StaticClass(), [&WorldsToEOFUpdate](UObject* WorldObj)
{
UWorld* World = CastChecked<UWorld>(WorldObj);
if (World->HasEndOfFrameUpdates())
{
WorldsToEOFUpdate.Add(World);
}
});
- UGameplayStatic一众方法
推荐使用UGameplayStatics::GetAllActorsOfClassWithTag、UGameplayStatics::GetAllActorsOfClass,尽量避免使用UGameplayStatics::GetAllActorsWithTag、UGameplayStatics::GetAllActorsWithInterface。
赶紧去检查自己项目有没有类似问题吧!
引擎代码提交
提交的PR,部分已经Merge到官方Github了。
- FBehaviorTreeDebugger https://github.com/EpicGames/UnrealEngine/pull/12608
- FUsdStageModule https://github.com/EpicGames/UnrealEngine/pull/12612
- SNiagaraBaselineViewport https://github.com/EpicGames/UnrealEngine/commit/acbe3976d6083f09fc6c7f0e804013c91ad8060c
- FControlRigEditMode https://github.com/EpicGames/UnrealEngine/pull/12611
- FReplayHelper https://github.com/EpicGames/UnrealEngine/pull/12614
- LevelEditorActions.cpp https://github.com/EpicGames/UnrealEngine/pull/12615
- UReflectionCaptureComponent https://github.com/EpicGames/UnrealEngine/pull/12616