背后的原理
先看FActorIterator的定义
/*** Actor iterator* Note that when Playing In Editor, this will find actors only in CurrentWorld*/
class FActorIterator : public TActorIteratorBase<FActorIterator>
{//.....
}
找到基类TActorIteratorBase
/*** 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 settingsif (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的代码
/*-----------------------------------------------------------------------------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 arrayObjectArray.Reserve(NumActors);// Fill the arrayfor (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的代码。
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 hundredsTArray<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则是在遍历所有这些子类的实例对象。代码如下:
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。示例如下:
for (TActorIterator<AInstancedFoliageActor> It(InWorld); It; ++It)
{AInstancedFoliageActor* IFA = *It;if (IFA->GetLevel() == InLevel){IFA->PostApplyLevelOffset(InOffset, bWorldShift);}
}
- 对于UActorComponent或UObject子类
建议使用ForEachObjectOfClass或者GetObjectsOfClass。示例如下:
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