欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > 给UE5优化一丢丢编辑器性能

给UE5优化一丢丢编辑器性能

2025/4/19 9:36:41 来源:https://blog.csdn.net/u014560101/article/details/144161897  浏览:    关键词:给UE5优化一丢丢编辑器性能

背后的原理

先看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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词