FObjectIterator源码笔记

Posted by SlothSimon's Skytree on November 7, 2017

UE中使用的迭代器中有TObjectIteratorFObjectIterator,前者继承自后者,加诸了模板特性。

文中虽然用的UE4代码(因为其开源),但UE3的这个迭代器原理是一致的,而UE4稍加改进。

先从FObjectIterator注释看起。

/**

  • Class for iterating through all objects, including class default objects.
  • Note that when Playing In Editor, this will find objects in the
  • editor as well as the PIE world, in an indeterminate order. */

意即:

这个类会迭代所有对象,包括默认类对象。需要注意的是当在编辑器中运行游戏时,不仅会找到游戏中的对象还会找到编辑器中的对象,顺序不确定。

TObjectIterator则是

这个类用于迭代继承自给定类型的所有对象,不包含默认类对象。

构造函数:

/**
	 * Constructor
	 *
	 * @param	InClass						返回该类的对象或子类的对象
	 * @param	bOnlyGCedObjects			若为真,跳过所有永久对象
	 * @param	AdditionalExclusionFlags	具有对应RF_* flags的对象应被排除
	 */
	FObjectIterator(UClass* InClass = UObject::StaticClass(), bool bOnlyGCedObjects = false, EObjectFlags AdditionalExclusionFlags = RF_NoFlags, EInternalObjectFlags InInternalExclusionFlags = EInternalObjectFlags::None) 
		: FUObjectArray::TIterator(GUObjectArray, bOnlyGCedObjects)
		, Class(InClass)
		, ExclusionFlags(AdditionalExclusionFlags)
		, InternalExclusionFlags(InInternalExclusionFlags)
	{
		// 我们不会返回后台正在加载的对象,除非是在异步加载中。

		InternalExclusionFlags |= EInternalObjectFlags::Unreachable;
		if (!IsInAsyncLoadingThread())
		{
			InternalExclusionFlags |= EInternalObjectFlags::AsyncLoading;
		}
		check(Class);

		do
		{
			UObject *Object = **this;       // 先将迭代器指针变成迭代器,再变成对象指针。

			if (!(Object->HasAnyFlags(ExclusionFlags) || Object->HasAnyInternalFlags(InternalExclusionFlags) || (Class != UObject::StaticClass() && !Object->IsA(Class))))
			{
				break;
			}
		} while(Advance());
	}

其中,InternalExclusionFlags |= EInternalObjectFlags::AsyncLoading有些费解,毕竟从上下文看来,既然都不异步加载了,还有必要排除异步加载的对象吗?

再看IsInAsyncLoadingThread()EInternalObjectFlags::AsyncLoading的定义。

/** @return True if called from the async loading thread if it's enabled, otherwise if called from game thread while is async loading code. */
extern CORE_API bool (*IsInAsyncLoadingThread)();

enum class EInternalObjectFlags : int32
{
    ...
    AsyncLoading = 1 << 27, ///< Object is being asynchronously loaded.		
    ...
};

于是明白,IsInAsyncLoadingThread()指的是当前代码运行所在线程是否异步,而不是指整个程序都不是异步加载。 根据UDK官方文档

在异步加载过程中创建的对象将被标记为RF_AsyncLoading,因为直到加载完成之前,对于引擎的其它部分来说,它们处于隐藏状态。

也就是说,在游戏运行过程中,有主线程和其他线程,其中包含了异步加载的线程;游戏中存在着加载完毕的对象、异步加载中的对象等。

对于非异步加载的线程来说,异步加载中的对象是隐藏的,不应该被获取的,所以InternalExclusionFlags要加上AsyncLoading,迭代时碰到有此Flag的对象就跳过;而对于异步加载的线程来说,异步加载中的对象是可见的,可以被获取进行操作

表面的原理了解了,但背后为什么这么设计尚不清楚,还需要对UE引擎的异步加载进行深入学习。

另外,UE3中AdditionalExclusionFlagsInInternalExclusionFlags是一个变量,UE4为什么将其分为两个部分,尚未可知。

值得吐槽的是,UE3这里的代码写的极其精炼,只用一行while();就完成了UE4中do{}while()的工作,复杂的while条件愣是让我看了好一会儿才理解。不得不说,代码的小而精炼和易读性通常是不可兼得。作为一个开源项目,此时易读性更为重要。

最后,提一些此类型代码细节:

  1. FObjectIterator作为一个经常被使用的工具,它的实现会影响性能,因此其中很多函数都加了FORCEINLINE标识符
  2. UE3用++Index而UE4用Advance()替代了,与时俱进跟随C++11新标准。
  3. check()在development模式下运行,而checkSlow只在debug模式下运行。[信息来源]