给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 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的代码

/*-----------------------------------------------------------------------------
	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的代码。

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则是在遍历所有这些子类的实例对象。代码如下:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/926136.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Q3营收同比增22.4%,即时配送高质量增长的美团未来何在?

首先&#xff0c;美团核心本地商业的稳健发展为其未来奠定了坚实的基础。核心本地商业营收达694亿元&#xff0c;同比增长20.2%&#xff0c;这显示出美团在本地生活服务领域的强大竞争力。随着中国经济的高质量发展和消费信心的提升&#xff0c;美团的年交易用户数、年活跃商户…

基于R语言森林生态系统结构、功能与稳定性分析与可视化

在生态学研究中&#xff0c;森林生态系统的结构、功能与稳定性是核心研究内容之一。这些方面不仅关系到森林动态变化和物种多样性&#xff0c;还直接影响森林提供的生态服务功能及其应对环境变化的能力。森林生态系统的结构主要包括物种组成、树种多样性、树木的空间分布与密度…

2024 ccpc 辽宁省赛 E(构造 思维?)L(二分+一点点数论知识?)

E 题意&#xff1a; 可以注意到&#xff1a; 我的两种方格都四个方格的大小。 所以 如果存在一种摆放方式 那么 4|nm。 再考虑一种特殊的情况 22 &#xff0c;此时虽然我的积是4 但是无法摆放的。 1>对于 4 | n,或者 4 | m.我直接摆放第二种方格就可以了。 如果我n 是4 的…

Leetcode 二叉树的锯齿形层序遍历

算法思想&#xff1a; 这段代码实现了 二叉树的锯齿形层序遍历&#xff0c;其核心思想是基于广度优先搜索&#xff08;BFS&#xff09;进行层序遍历&#xff0c;并根据当前层数决定从左到右或从右到左的顺序来组织每一层的节点值。 level.add 和 level.addFirst 有点类似单链…

c++哈希表(原理、实现、开放寻址法)适合新手

c系列哈希的原理及实现&#xff08;上&#xff09; 文章目录 c系列哈希的原理及实现&#xff08;上&#xff09;前言一、哈希的概念二、哈希冲突三、哈希冲突解决3.1、开放寻址法3.2、删除操作3.3、负载因子四、代码实现 总结 前言 红黑树平衡树和哈希有不同的用途。 红黑树、…

用MATLAB符号工具建立机器人的动力学模型

目录 介绍代码功能演示拉格朗日方法回顾求解符号表达式数值求解 介绍 开发机器人过程中经常需要用牛顿-拉格朗日法建立机器人的动力学模型&#xff0c;表示为二阶微分方程组。本文以一个二杆系统为例&#xff0c;介绍如何用MATLAB符号工具得到微分方程表达式&#xff0c;只需要…

MongoDB集群分片安装部署手册

文章目录 一、集群规划1.1 集群安装规划1.2 端口规划1.3 目录创建 二、mongodb安装&#xff08;三台均需要操作&#xff09;2.1 下载、解压2.2 配置环境变量 三、mongodb组件配置3.1 配置config server的副本集3.1.1 config配置文件3.1.2 config server启动3.1.3 初始化config …

java 调用 k8s crd 生成 crd model

k8s官方提供了自动生成Java模型代码的工具&#xff0c;使用指南&#xff1a; https://github.com/kubernetes-client/java提供有两种方法&#xff1a; github action远程生成本地docker镜像生成 本地docker镜像生成很简单&#xff0c;跟着官方指南下载镜像执行命令即可&…

36 基于单片机的电磁炉系统设计

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过DS18B20温度传感器检测温度&#xff0c;通过八位数码管显示&#xff0c; 如果温度超过阈值&#xff0c;则蜂鸣器报警&#xff0c;红灯亮起&#xff1b;若不超过阈值&…

IoTDB 常见问题 QA 第一期

开始&#xff01;关于 IoTDB 的 Q&A 我们将定期汇总社区讨论频繁的问题&#xff0c;并展开进行详细回答&#xff0c;通过积累常见问题“小百科”&#xff0c;方便大家使用 IoTDB。 Q1&#xff1a;WAL 堆积导致写入失败 问题及现象 集群报错&#xff1a; The write is rejec…

buildroot 制作Linux嵌入式文件系统,并添加telnet 以及ssh

在开始配置前&#xff0c;我们需要了解SSH和Telnet的基本概念。SSH&#xff08;Secure Shell&#xff09;为加密的网络协议&#xff0c;用于在不安全的网络中执行命令并管理网络服务。相对于SSH&#xff0c;Telnet是一个老旧且非加密的协议&#xff0c;用于进行远程登录 sshd 服…

Simulink的SIL软件在环测试

以基于模型的设计&#xff08;MBD&#xff09;的软件开发时&#xff0c;需要进行SIL&#xff08;软件在环测试&#xff09;。SIL测试就是在PC上验证模型是否与代码功能一致。在项目开展中&#xff0c;用在需要将控制器生成移植到硬件前&#xff0c;把控制器的模块生成代码&…

【赵渝强老师】PostgreSQL中的模式

在PostgreSQL中&#xff0c;所有的数据库对象都是属于模式中的对象。这里的数据库对象包括&#xff1a;表、索引、视图、存储过程、触发器等等。所有数据库对象都有各自的对象标识符oid&#xff08;object identifiers&#xff09;,它是一个无符号的四字节整数&#xff0c;相关…

【分页查询】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

Android 图形系统之四:Choreographer

Choreographer 是 Android 系统中负责帧同步的核心组件&#xff0c;它协调输入事件、动画和绘制任务&#xff0c;以确保界面以固定频率&#xff08;通常是每 16ms&#xff0c;一帧&#xff09;流畅渲染。通过管理 VSYNC 信号和调度任务&#xff0c;Choreographer 是实现流畅 UI…

计算机毕业设计Python异常流量检测 流量分类 流量分析 网络流量分析与可视化系统 网络安全 信息安全 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

关于扩散方程的解

1-D 扩散方程的形式 Cauchy齐次方程 这个解无积分无级数&#xff0c;很简单的形式 美其名曰&#xff1a;基本解。 把基本解和初值做卷积&#xff0c;就得到cauchy方程的解。

零基础学安全--Burp Suite(4)proxy模块以及漏洞测试理论

目录 学习连接 一些思路 proxy模块 所在位置 功能简介 使用例子 抓包有一个很重要的点&#xff0c;就是我们可以看到一些在浏览器中看不到的传参点&#xff0c;传参点越多就意味着攻击面越广 学习连接 声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可…

python打包深度学习虚拟环境

今天师兄让我把环境打包发给他&#xff0c;我才知道可以直接打包深度学习虚拟环境&#xff0c;这样另一个人就不用辛辛苦苦的去装环境了&#xff0c;我们都知道有些论文他需要的环境很难装上。比如装Apex&#xff0c;装 DCN&#xff0c;mmcv-full 我现在把3090机子上的ppft虚拟…

M4V 视频是一种什么格式?如何把 M4V 转为 MP4 格式?

M4V 是一种视频文件格式&#xff0c;主要由苹果公司用于其产品和服务中&#xff0c;如 iTunes Store 上的电影和电视节目。这种格式可以包含受版权保护的内容&#xff0c;并且通常与苹果的 DRM&#xff08;数字版权管理&#xff09;技术结合使用&#xff0c;以限制内容的复制和…