ue 为啥要用TObjectPtr<T>

TObjectPtr 好处

  • 懒加载(什么是TObjectPtr 的懒加载,在什么时机会进行加载,怎么判断是否加载过)
  • 安全(访问数据安全性,释放的时候其它引用对象是否会安全释放)
  • 为啥奇数DebugPtr 表示没加载过(不知)

查看源码:
TObjectPtr 是个模板类型,他把T类型的数据存储为FObjectPtr 成员,并提供数据的访问,数据的访问通过FObjectPtr 的Get 方法

template <typename T>
struct TObjectPtr
{
	explicit FORCEINLINE TObjectPtr(FObjectPtr ObjPtr)
		: ObjectPtr(ObjPtr)
	{
	}
	FORCEINLINE TObjectPtr(T& Object)
		: ObjectPtr(const_cast<std::remove_const_t<T>*>(&Object))
	{
	}
	/
	/ 访问数据
	FORCEINLINE T* Get() const { return (T*)ObjectPtr.Get(); }
	// 类型
	FORCEINLINE UClass* GetClass() const { return ObjectPtr.GetClass(); }
	// outer 对象
	FORCEINLINE TObjectPtr<UObject> GetOuter() const
	{ 
		FObjectPtr Ptr = ObjectPtr.GetOuter();
		return TObjectPtr<UObject>(Ptr);
	}
	// 包对象
	FORCEINLINE TObjectPtr<UPackage> GetPackage() const
	{
		FObjectPtr Ptr = ObjectPtr.GetPackage();
		return TObjectPtr<UPackage>(Ptr);
	}

	// 访问都是通过
	Get 函数进行访问
	FORCEINLINE T* operator->() const { return Get(); }	
	FORCEINLINE UObject& operator*() const { return *Get(); }

	FORCEINLINE FObjectHandle GetHandle() const { return ObjectPtr.GetHandle(); }
	union
	{
		FObjectPtr ObjectPtr; // 指针数据内容
		T* DebugPtr; // 如果指针地址是奇数ObjectPtr还没解析过(快速判定指针数据是否解析过)
	};
}

FObjectPtr 数据,通过MakeObjectHandle 兼容指针或者对象的引用

struct FObjectPtr
{
public:
	// 这边只拷贝句柄,没有进行解析(MakeObjectHandle 只是进行c 格式类型强转)
	explicit FORCEINLINE FObjectPtr(UObject* Object)
		: Handle(UE::CoreUObject::Private::MakeObjectHandle(Object))
	{
	// 防止构造时机进入垃圾回收被删除(正常不需要写吧,gc 的时候主线程是停止的,不会再构造对象吧)
	// 业务逻辑上没在构造函数上用过这种写法
#if UE_OBJECT_PTR_GC_BARRIER
		ConditionallyMarkAsReachable(Object);
#endif // UE_OBJECT_PTR_GC_BARRIER
	}

	// Get 和GetClass 都会进行对象的解析
	FORCEINLINE UObject* Get() const
	{
		return UE::CoreUObject::Private::ResolveObjectHandle(Handle);
	}
	FORCEINLINE UClass* GetClass() const
	{
		return UE::CoreUObject::Private::ResolveObjectHandleClass(Handle);
	}
	
	// 数据成员
	union
	{
		mutable FObjectHandle Handle; // 是句柄(数据的索引)或者指针数据
		UObject* DebugPtr;  // 如果指针地址是奇数ObjectPtr还没解析过(快速判定指针数据是否解析过)
	};

FObjectHandle 数据解析称为对象的方法
在这里插入图片描述

可以看到数据是存放在FObjectHandle 中,分为懒加载和直接加载。对于懒加载有分为数据是否已经解析过还是需要进行解析,对于已经解析过的对象FObjectHandle 存放的时对象的指针

关于懒加载:WITH_EDITORONLY_DATA 的时候(编辑器模式下UE_WITH_OBJECT_HANDLE_LATE_RESOLVE=1)不需要构造的时候加载对象,用到Get()获取对象的时候加载。同时引擎提供对于对象名称FObjectPtr 中的取包名称,路径名称可以不去解析指针的内容,直接访问这些名称。这些是为了加快编辑器加载速度,对于打包版本因为不带WITH_EDITORONLY_DATA,TObjectPtr 会退化回UObject 不会有影响。
在这里插入图片描述

这里继续深入查看对象解析方法
PackedObjectRef 数据结构: 30位包对象ID | 32位对象ID | 1位(值3表示不安全)| 1位(值0表示数据已经解析)
在这里插入图片描述
通过解析到的数据从GObjectHandleIndex 表里面查找对象的名称和包名,并判断FObjectPathId 对象是否时WeakObj 对象进行返回。FObjectPathId是转换成FName 的方式,对大小写不敏感,路径少的时候可以不查表,后续再分析这个类型。如果是WeakObj 会拼接回下标从GUObjectArray里面找到UObject 对象。
对于名字数据又进行数据缓存,编辑器可以在不解析对象的情况下直接访问
在这里插入图片描述
对于FObjectPathId 还没找到对象的数据通过下面的方式进行加载


UObject* FObjectRef::Resolve(uint32 LoadFlags /*= LOAD_None*/) const
{
	TRACE_CPUPROFILER_EVENT_SCOPE(ResolveObjectRef);

	UE::CoreUObject::Private::FObjectPathId ObjectPath = GetObjectPath();
	if (IsNull() || !ObjectPath.IsValid())
	{
		UE::CoreUObject::Private::OnReferenceResolved(*this, nullptr, nullptr);
		return nullptr;
	}

	bool bWasObjectOrPackageLoaded = false;
	// 加载包对象
	UPackage* TargetPackage = FindOrLoadPackage(PackageName, LoadFlags, bWasObjectOrPackageLoaded);

	if (!TargetPackage)
	{
		UE::CoreUObject::Private::OnReferenceResolved(*this, nullptr, nullptr);
		return nullptr;
	}

	UE::CoreUObject::Private::FObjectPathId::ResolvedNameContainerType ResolvedNames;
	ObjectPath.Resolve(ResolvedNames);

	UObject* CurrentObject = TargetPackage;
	for (int32 ObjectPathIndex = 0; ObjectPathIndex < ResolvedNames.Num(); ++ObjectPathIndex)
	{
		UObject* PreviousOuter = CurrentObject;
		CurrentObject = StaticFindObjectFastInternal(nullptr, CurrentObject, ResolvedNames[ObjectPathIndex]);
		
		// 包重定向
		if (UObjectRedirector* Redirector = dynamic_cast<UObjectRedirector*>(CurrentObject))
		{
			CurrentObject = Redirector->DestinationObject;
			if (CurrentObject != nullptr)
			{
				TargetPackage = CurrentObject->GetPackage();
			}
		}

		if (!CurrentObject && !TargetPackage->IsFullyLoaded() && TargetPackage->GetLinker() && TargetPackage->GetLinker()->IsLoading())
		{
			if (IsInAsyncLoadingThread() || IsInGameThread())
			{
				// 加载包的所有对象
				TargetPackage->GetLinker()->LoadAllObjects(true);
				bWasObjectOrPackageLoaded = true;
			}
			else
			{
				// Shunt the load request to happen on the game thread and block on its completion.  This is a deadlock risk!  The game thread may be blocked waiting on this thread.
				UE_LOG(LogObjectRef, Warning, TEXT("Resolve of object in package '%s' from a non-game thread was shunted to the game thread."), *PackageName.ToString());
				TGraphTask<FFullyLoadPackageOnHandleResolveTask>::CreateTask().ConstructAndDispatchWhenReady(TargetPackage)->Wait();
				bWasObjectOrPackageLoaded = true;
			}

			// 加载包里面指定位置的对象
			CurrentObject = StaticFindObjectFastInternal(nullptr, PreviousOuter, ResolvedNames[ObjectPathIndex]);
			if (UObjectRedirector* Redirector = dynamic_cast<UObjectRedirector*>(CurrentObject))
			{
				// 处理对象重定向
				CurrentObject = Redirector->DestinationObject;
				if (CurrentObject != nullptr)
				{
					TargetPackage = CurrentObject->GetPackage();
				}
			}
		}

		if (!CurrentObject)
		{
			UE::CoreUObject::Private::OnReferenceResolved(*this, TargetPackage, nullptr);
			return nullptr;
		}
	}

	if (CurrentObject->HasAnyFlags(RF_NeedLoad) && TargetPackage->GetLinker())
	{
		if (IsInAsyncLoadingThread() || IsInGameThread())
		{
			TargetPackage->GetLinker()->LoadAllObjects(true);
			bWasObjectOrPackageLoaded = true;
		}
		else
		{
			// Shunt the load request to happen on the game thread and block on its completion.  This is a deadlock risk!  The game thread may be blocked waiting on this thread.
			UE_LOG(LogObjectRef, Warning, TEXT("Resolve of object in package '%s' from a non-game thread was shunted to the game thread."), *PackageName.ToString());
			TGraphTask<FFullyLoadPackageOnHandleResolveTask>::CreateTask().ConstructAndDispatchWhenReady(TargetPackage)->Wait();
			bWasObjectOrPackageLoaded = true;
		}
	}
	UE::CoreUObject::Private::OnReferenceResolved(*this, TargetPackage, CurrentObject);

	if (bWasObjectOrPackageLoaded)
	{
		UE::CoreUObject::Private::OnReferenceLoaded(*this, TargetPackage, CurrentObject);
	}

	return CurrentObject;
}

关于安全:不带WITH_EDITORONLY_DATA 的版本(打包DS模式或者客户端模式)等价于UObject ,没有更加安全。对于编辑器模式指针访问都是走Get 函数,Get 里面会用路径从全局对象里面判断是否有效,无效的话返回的是空指针。不会出现野指针情况。

和UPROPERTY 区别
UPROPERTY 会把对象加到GC 进行管理,每隔一段时间都会有GC检测会带来消耗,但是打包版本还是会有引用对象可靠性保证,引用对象如果消耗会通知这里变为空指针。普通情况下是直接判空,保险情况下用IsValid(Object) 去全局对象表判断对象是否安全

TObjectPtr 没有GC 管理,没有GC消耗,直接判空可能会出问题,需要每次访问都用xxx.IsValid() 进行判断

TObjectPtr 对象也可以加UPROPERTY 进行对象的引用

对于对象因为非法访问内存崩溃的可以加stompmalloc 方式定位出问题堆栈进行排查

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值