UE4 对象类型Class及内存管理

(1)

UE4中的对象在内存中的表示目前大概可分为三类:·

A、普通的C++类型 F Class

B、智能指针

C、UObject类型的UClass

我们逐一说明他们的使用方法和注意事项

1、针对普通的C++的class照普通的使用方式即可;UED中或者UE4的底层很多使用这种Class的例子;但UE4会统一的在前加F 比如:上次我们说的负责坐标轴绘制和处理的FWidget类(在UnrealWidget.h中)

声明:

class FWidget
	: public FGCObject
{
FWidget();
}

使用:

FEditorViewportClient::FEditorViewportClient(FEditorModeTools* InModeTools, FPreviewScene* InPreviewScene, const TWeakPtr<SEditorViewport>& InEditorViewportWidget)
	: bAllowCinematicPreview(false)
...
, Widget(new FWidget)


销毁:

FEditorViewportClient::~FEditorViewportClient()
{
	...
	delete Widget;
}

目前大部分的F Class都在UED的框架中使用。在game的框架中相同功能的Class都改成了UClass

2、UE4的智能指针SmartPointer体系在引擎初期项目中用处主要在UI上;SlateUI是主要使用智能指针的地方。但随着后期UMG的出现SlateUI的部分被完全封装在UMG内部,现在研发人员一般不会接触到这一块。但对于研究引擎的人来说熟悉智能指针相当重要。以SWidget为例说明

声明方式():

class SLATECORE_API SWidget
	: public FSlateControlledConstruction,
	  public TSharedFromThis<SWidget>		// Enables 'this->AsShared()'
{
	friend struct FCurveSequence;
	//dubeibei XMod
	friend class UXScriptWidget;
public:
}

重点是类模版TSharedFromThis<SWidget>;TSharedPtr<>;TSharedRef<>

template< class ObjectType, ESPMode Mode >
class TSharedFromThis
{};

template< class ObjectType, ESPMode Mode >
class TSharedPtr
{};

template< class ObjectType, ESPMode Mode >
class TSharedRef
{};

template< class ObjectType, ESPMode Mode >
class TWeakPtr
{
public:
};


详情见:SharedPointer.h 其中指针会有SharedReferenceCount保存计数;计数为0时自动清理。TWeakPtr中有WeakReferenceCount保存计数,但TWeakPtr的引用计数不作为是否清理的标记,即TweakPtr中的Object可能已经被清理;但TweakPtr还在;可使用TWeakPtr的Isvalid来校验Object是否已被清理。

使用方式:

TSharedPtr<SWidget> RetWidget = SNew(SInvalidationPanel)

或者:

SAssignNew(MyTextBlock, STextBlock)

或者:

MakeShareable(new FUIController()); 返回的也是一个TSharedPtr

关于TSharedPtr TSharedRef TWeakPtr之间相互转换的使用:

TSharedPtr ToSharedRef()-> TSharedRef

TWeakPtr Pin()-> TSharedPtr

TSharedRef

转换使用StaticCastSharedPtr:

TSharedPtr<SChannelListPageWidget> MyWidget = StaticCastSharedPtr<SChannelListPageWidget>(GetWidget());

3、关于 UObject的Class使用

UObject是上层或者Game框架里面最经常用到的Class。把游戏框架中出现的各种东西抽象出一个UObject十分必要;通用的属性和接口;统一的GC;方便数据统计;等等

声明示例如下:

UCLASS(config = Game, meta = (ChildCanTick))
class PROJECTZ_API AMaterialParamaterObject : public AActor
{
	GENERATED_UCLASS_BODY()

	/** The CapsuleComponent being used for movement collision (by CharacterMovement). Always treated as being vertically aligned in simple collision check functions. */
	UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly)
	class USceneComponent* RootSceneCom;

	/** The main skeletal mesh associated with this Character (optional sub-object). */
	UPROPERTY(Category = MapEditor, VisibleAnywhere, BlueprintReadOnly)
	class UStaticMeshComponent* Mesh;

	static FName MeshComponentName;
	static FName RootComponentName;

	UFUNCTION(BlueprintCallable, Category = "MapEditor|Objects")
	void ChangeFloatParamater(int32 ParaIndex, int32 NameIndex, float InValue);

	UFUNCTION(BlueprintCallable, Category = "MapEditor|Objects")
	void ChangeIntParamater(int32 ParaIndex, int32 NameIndex, int32 InValue);

	UFUNCTION(BlueprintCallable, Category = "MapEditor|Objects")
	void ChangeVectorParamater(int32 ParaIndex, int32 NameIndex, FVector InValue);

	UFUNCTION(BlueprintCallable, Category = "MapEditor|Objects")
	void ChangeTextureParamater(int32 ParaIndex, int32 NameIndex, UTexture2D* InValue);

	UPROPERTY(Category = "MapEditor|Objects", EditAnywhere, BlueprintReadWrite)
	TArray<FEditorMaterialParamater> Paramaters;

};

关于UHT:UnrealHeaderTool,UHT会根据UClass的头文件.h生成.generated.h文件把其中拥有UPROPERTY UFUNCTION的标签的重新定义例如ChangeFloatParamater接口:

DECLARE_FUNCTION(execChangeFloatParamater) \
	{ \
		P_GET_PROPERTY(UIntProperty,Z_Param_ParaIndex); \
		P_GET_PROPERTY(UIntProperty,Z_Param_NameIndex); \
		P_GET_PROPERTY(UFloatProperty,Z_Param_InValue); \
		P_FINISH; \
		P_NATIVE_BEGIN; \
		this->ChangeFloatParamater(Z_Param_ParaIndex,Z_Param_NameIndex,Z_Param_InValue); \
		P_NATIVE_END; \

	}



新生成的.genreated.h文件会加入编译所有的UPROPERTY和UFUNCTION都会是继承自UObject的Class使用 ;然后再由GC统一的管理UObject;

不加UPROPETY标签的属性不在UObject 此过程GC的范围之内;需要另外的控制,不然很容易内存泄漏。见下章详细说明。

使用:

MyGCProtectedObj = NewObject<UMyObjectClass>(this);
如果不想让UObject强制回收可设置:
YourObjectInstance->AddToRoot();

销毁:

if(!MyObject) return;
if(!MyObject->IsValidLowLevel()) return;
 
MyObject->ConditionalBeginDestroy(); //instantly clears UObject out of memory
MyObject = nullptr;

销毁Actor:
if(!TheCharacter) return;
if(!TheCharacter->IsValidLowLevel()) return;
TheCharacter->Destroy();
TheCharacter->ConditionalBeginDestroy(); //essential extra step, in all my testing

(2)

在所有的对象保存和内存的问题中最重点的两个问题就是:不同对象指针间的互相保存和UObject的GC机制。除去F Class的对象和智能指针的对象之外有自己的内存管理方式之外(上篇文章已经分析),UObject对象的内存管理完全交由GarbageCollection完成。

1、实施GC的关键因素是由Object间的引用关系决定的UObject状态。我们先从上层分析UObject的引用关系和类型之间相互引用的注意事项:

A、UObject间的引用关系需要用指针强引用加UPROPERTY标签完成例如:

UCLASS(abstract, notplaceable, NotBlueprintable, HideCategories=(Collision,Rendering,"Utilities|Transformation")) 
class ENGINE_API AController : public AActor, public INavAgentInterface
{
	GENERATED_UCLASS_BODY()

private:
	/** Pawn currently being controlled by this controller.  Use Pawn.Possess() to take control of a pawn */
	UPROPERTY(replicatedUsing=OnRep_Pawn)
	APawn* Pawn;
...
}

UPROPERTY标签生成UProperty对象,UProperty对象可以控制对属性的访问等。也通过UProperty对象保存引用关系

不加UPROPERTY的如果想持久的保存指针的话,需要重写UObject的AddReferencedObjects接口为UObject对象添加引用。例如AActor类中为非UPROPERTY的成员变量OwnedComponents添加reference:

void AActor::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
	AActor* This = CastChecked<AActor>(InThis);
	Collector.AddReferencedObjects(This->OwnedComponents);
#if WITH_EDITOR
	if (This->CurrentTransactionAnnotation.IsValid())
	{
		This->CurrentTransactionAnnotation->AddReferencedObjects(Collector);
	}
#endif
	Super::AddReferencedObjects(InThis, Collector);
}

B、引擎中的代码UCLass类引用的非Object对象的变量比如UStruct UEnum 或者FVector等可是数据类型而定,如果是BlurprintType的类型;则需要写成UPROPERTY才能使用UObject的GC机制进行管理;目前BlurprintType的结构体,枚举都会被引擎编译成UStruct UEnum的对象。

C、F Class引用UObject的时候注意;为避免内存泄漏FClass应继承与FGCObject,并实现AddReferencedObjects接口,在接口中给UObject的指针添加reference例如FWidget中:

class FWidget
	: public FGCObject
{
public:
...
private:
       UMaterialInterface* TransparentPlaneMaterialXY;
	UMaterialInterface* GridMaterial;

	UMaterialInstanceDynamic* AxisMaterialX;
	UMaterialInstanceDynamic* AxisMaterialY;
	UMaterialInstanceDynamic* AxisMaterialZ;
	UMaterialInstanceDynamic* CurrentAxisMaterial;

}
void FWidget::AddReferencedObjects( FReferenceCollector& Collector )
{
	Collector.AddReferencedObject( AxisMaterialX );
	Collector.AddReferencedObject( AxisMaterialY );
	Collector.AddReferencedObject( AxisMaterialZ );
	Collector.AddReferencedObject( OpaquePlaneMaterialXY );
	Collector.AddReferencedObject( TransparentPlaneMaterialXY );
	Collector.AddReferencedObject( GridMaterial );
	Collector.AddReferencedObject( CurrentAxisMaterial );
}

D、智能指针一般不持久保存UObject的指针对象如果有需求我的习惯是使用TWeakObjectPtr的形式保存;可以用Isvalid先判断指针是否有效,但是因为是weakreference不对GC产生阻止的作用。

2、下面说下我对于UObject的GarbageCollection的理解,代码太多我不一定理解的全面。

A、先说最基本的UObject对象的内存空间的分配和释放;目前FUObjectAllocator处理UObject对象的内存分配和回收 不论上层多复杂但归根结底都要到关于内存的问题都会归到这里。比如我们runtime的时候执行destroy的操作,这时其实对象所占的空间依然存在,如果有指针引用到该对象的话我们可以看到内容为none;指针并不是nullptr;只有等执行了GC之后才会真正执行释放。同样UObject实例的时候会分配内存

GUObjectAllocator.FreeUObject(Object);
Obj = (UObject *)GUObjectAllocator.AllocateUObject(TotalSize,Alignment,GIsInitialLoad);

B、综上GC的最终目标其实就是对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage在../Private\UObject\GarbageCollection.cpp

C、关于UObject的状态标记Flag

/** Objects flags for internal use (GC, low level UObject code) */
enum class EInternalObjectFlags : int32
{
	None = 0,
	// All the other bits are reserved, DO NOT ADD NEW FLAGS HERE!
	ReachableInCluster = 1 << 23, /// External reference to object in cluster exists
	ClusterRoot = 1 << 24, ///< Root of a cluster
	Native = 1 << 25, ///< Native (UClass only).
	Async = 1 << 26, ///< Object exists only on a different thread than the game thread.
	AsyncLoading = 1 << 27, ///< Object is being asynchronously loaded.
	Unreachable = 1 << 28, ///< Object is not reachable on the object graph.
	PendingKill = 1 << 29, ///< Objects that are pending destruction (invalid for gameplay but valid objects)
	RootSet = 1 << 30, ///< Object will not be garbage collected, even if unreferenced.
	NoStrongReference = 1 << 31, ///< The object is not referenced by any strong reference. The flag is used by GC.

	GarbageCollectionKeepFlags = Native | Async | AsyncLoading,
	// Make sure this is up to date!
	AllFlags = ReachableInCluster | ClusterRoot | Native | Async | AsyncLoading | Unreachable | PendingKill | RootSet | NoStrongReference
};

flag作为GC的状态保存在FUObjectItem中

D、FUObjectItem和FUObjectArray以及UGCObjectReferencer

FUObjectItem封装了单个的UObject指针在FUObjectArray中;FUObjectArray包含所有的live的Object对象

FUObjectItem:

/**
* Single item in the UObject array.
*/
struct FUObjectItem
{
// Pointer to the allocated object
class UObjectBase* Object;
// Internal flags
int32 Flags;
...
};
FFixedUObjectArray:
class FFixedUObjectArray
{
	/** Static master table to chunks of pointers **/
	FUObjectItem* Objects;
...
};

FUObjectArray:

class COREUOBJECT_API FUObjectArray
{
public:
...
//typedef TStaticIndirectArrayThreadSafeRead<UObjectBase, 8 * 1024 * 1024 /* Max 8M UObjects */, 16384 /* allocated in 64K/128K chunks */ > TUObjectArray;
	typedef FFixedUObjectArray TUObjectArray;
...
TUObjectArray ObjObjects;
...
};

UGCObjectReferencer为非UClass的对象引用UObject对象指针提供了宿主;从而构成引用关系;GCObjectReference不依赖与任何对象因为设置了RootSet

void Init()
	{
		// Some objects can get created after the engine started shutting down (lazy init of singletons etc).
		if (!GIsRequestingExit)
		{
			StaticInit();
			check(GGCObjectReferencer);
			// Add this instance to the referencer's list
			GGCObjectReferencer->AddObject(this);
		}
	}

在执行GC CollectReference的过程中会把非UClass类的Uobject指针的引用关系指到GGCObjectReferencer上从而保证Uobject不会被清理。

E、分析GC的过程

入口CollectGarbageInternal GarbageCollection.cpp

/** 
 * Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set
 *
 * @param	KeepFlags			objects with those flags will be kept regardless of being referenced or not
 * @param	bPerformFullPurge	if true, perform a full purge after the mark pass
 */
void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
...
};

关键的部分在ReachabilityAnalysis和执行IncrementalPurgeGarbage

ReachabilityAnalysis:

执行reachability分析的入口在CollectGarbageInternal 中

const double StartTime = FPlatformTime::Seconds();
			FRealtimeGC TagUsedRealtimeGC;
			TagUsedRealtimeGC.PerformReachabilityAnalysis(KeepFlags, bForceSingleThreadedGC);
			UE_LOG(LogGarbage, Log, TEXT("%f ms for GC"), (FPlatformTime::Seconds() - StartTime) * 1000);

先执行MarkObjectsAsUnreachable把异常的ObjectItem状态修改:

ObjectItem->SetFlags(EInternalObjectFlags::Unreachable | EInternalObjectFlags::NoStrongReference);

正常的放进ObjectsToSerialize

然后执行CollectReferences ,遍历ObjectsToSerialize中的Object;从中取出ReferenceTokenStream

// Get pointer to token stream and jump to the start.
FGCReferenceTokenStream* RESTRICT TokenStream = &CurrentObject->GetClass()->ReferenceTokenStream;

然后遍历referenceTokenStream中的ReferenceInfo

FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex);

再根据不同的referencetype执行ReferenceProcessor的HandleTokenStreamObjectReference

或者执行AddreferenceObject 构成ClusterReference

这步处理的结果会把一些ReferencedClusterRootObjectItem的EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable flag清理掉

ReferencedClusterRootObjectItem->ClearFlags(EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable);

IncrementalPurgeGarbage:

入口在CollectGarbageInternal 中:

if (bPerformFullPurge || GIsEditor)

{

IncrementalPurgeGarbage(false);

}

分为两个过程对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;和另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage

if (ObjectItem->IsUnreachable())
			{
				UObject* Object = static_cast<UObject*>(ObjectItem->Object);
				// Object should always have had BeginDestroy called on it and never already be destroyed
				check( Object->HasAnyFlags( RF_BeginDestroyed ) && !Object->HasAnyFlags( RF_FinishDestroyed ) );

				// Only proceed with destroying the object if the asynchronous cleanup started by BeginDestroy has finished.
				if(Object->IsReadyForFinishDestroy())
				{
#if PERF_DETAILED_PER_CLASS_GC_STATS
					// Keep track of how many objects of a certain class we're purging.
					const FName& ClassName = Object->GetClass()->GetFName();
					int32 InstanceCount = GClassToPurgeCountMap.FindRef( ClassName );
					GClassToPurgeCountMap.Add( ClassName, ++InstanceCount );
#endif
					// Send FinishDestroy message.
					Object->ConditionalFinishDestroy();
				}
				else
				{
					// The object isn't ready for FinishDestroy to be called yet.  This is common in the
					// case of a graphics resource that is waiting for the render thread "release fence"
					// to complete.  Just calling IsReadyForFinishDestroy may begin the process of releasing
					// a resource, so we don't want to block iteration while waiting on the render thread.

					// Add the object index to our list of objects to revisit after we process everything else
					GGCObjectsPendingDestruction.Add(Object);
					GGCObjectsPendingDestructionCount++;
				}
			}




free的过程

FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex;
			checkSlow(ObjectItem);
			if (ObjectItem->IsUnreachable())
			{
				UObject* Object = (UObject*)ObjectItem->Object;
				check(Object->HasAllFlags(RF_FinishDestroyed|RF_BeginDestroyed));
				GIsPurgingObject				= true; 
				Object->~UObject();
				GUObjectAllocator.FreeUObject(Object);
				GIsPurgingObject				= false;
				// Keep track of purged stats.
				GPurgedObjectCountSinceLastMarkPhase++;
			}

猜你喜欢

转载自blog.csdn.net/luofeixiongsix/article/details/80100180
今日推荐