虚幻WorldPartition世界分区源码分析1

Table of Contents

  1. 世界分区源码分析(一)
    1. 起始
    2. 世界分区管理者-UWorldPartitionRuntimeSpatialHash
    3. 网格对象(Grid)
    4. 最后

世界分区源码分析(一)

最近一直在写DS管理器的底层部分(BLEngine),已经实现底层数据结构,对象管理树,反射系统,基于TCP协议和Protobuf包管理的通信线程框架处理等。
接下来需要将虚幻的世界分区拆分成自定义粒度的多个DS服务器。这几天会陆续梳理世界分区的核心逻辑和数据结构,记录在这里,方便自己,也方便大家。
网上世界分区的资料比较少,也比较浅。只好来分析代码。

起始

世界分区的代码是从这个类入手的:UWorldPartition
先来看BeginPlay这个初始化函数

void UWorldPartition::OnBeginPlay()
{
        // 关键在这里
        GenerateStreaming(Params, Context);

        // Prepare GeneratedStreamingPackages
        check(GeneratedStreamingPackageNames.IsEmpty());
        for (const FString& PackageName : OutGeneratedStreamingPackageNames)
        {
                // Set as memory package to avoid wasting time in UWorldPartition::IsValidPackageName (GenerateStreaming for PIE runs on the editor world)
                FString Package = FPaths::RemoveDuplicateSlashes(FPackageName::IsMemoryPackage(PackageName) ? PackageName : TEXT("/Memory/") + PackageName);
                GeneratedStreamingPackageNames.Add(Package);
        }

}

GenerateStreaming函数中,这个函数的主要功能是生成Streaming包,并将其加入到GeneratedStreamingPackageNames集合中。
这个函数是关键,具体实现我摘重要的记录一下。经过重重调用,关键实现在这里。

bool UWorldPartitionRuntimeSpatialHash::GenerateStreaming(UWorldPartitionStreamingPolicy* StreamingPolicy, const IStreamingGenerationContext* StreamingGenerationContext, TArray<FString>* OutPackagesToGenerate)
{

        UWorldPartition* WorldPartition = GetOuterUWorldPartition();

        if (!Grids.Num())
        {
                UE_LOG(LogWorldPartition, Error, TEXT("Invalid partition grids setup"));
                return false;
        }

        // Fix case where StreamingGrids might have been persisted.
        bIsNameToGridMappingDirty = true;
        StreamingGrids.Empty();

        // Append grids from ASpatialHashRuntimeGridInfo actors to runtime spatial hash grids
        TArray<FSpatialHashRuntimeGrid> AllGrids;
        AllGrids.Append(Grids);

        TArray<const FWorldPartitionActorDescView*> SpatialHashRuntimeGridInfos = StreamingGenerationContext->GetMainWorldContainer()->ActorDescViewMap->FindByExactNativeClass<ASpatialHashRuntimeGridInfo>();
        for (const FWorldPartitionActorDescView* SpatialHashRuntimeGridInfo : SpatialHashRuntimeGridInfos)
        {
                FWorldPartitionReference Ref(WorldPartition, SpatialHashRuntimeGridInfo->GetGuid());
                ASpatialHashRuntimeGridInfo* RuntimeGridActor = CastChecked<ASpatialHashRuntimeGridInfo>(SpatialHashRuntimeGridInfo->GetActor());

                if (const FSpatialHashRuntimeGrid* ExistingRuntimeGrid = AllGrids.FindByPredicate([RuntimeGridActor](const FSpatialHashRuntimeGrid& RuntimeGrid) { return RuntimeGrid.GridName == RuntimeGridActor->GridSettings.GridName; }))
                {
                        UE_LOG(LogWorldPartition, Log, TEXT("Got duplicated runtime grid actor '%s' for grid '%s'"), *SpatialHashRuntimeGridInfo->GetActorLabelOrName().ToString(), *RuntimeGridActor->GridSettings.GridName.ToString());
                        check(*ExistingRuntimeGrid == RuntimeGridActor->GridSettings);
                }
                else
                {
                        AllGrids.Add(RuntimeGridActor->GridSettings);
                }
        }

        TMap<FName, int32> GridsMapping;
        GridsMapping.Add(NAME_None, 0);
        for (int32 i = 0; i < AllGrids.Num(); i++)
        {
                const FSpatialHashRuntimeGrid& Grid = AllGrids[i];
                check(!GridsMapping.Contains(Grid.GridName));
                GridsMapping.Add(Grid.GridName, i);
        }

        TArray<TArray<const IStreamingGenerationContext::FActorSetInstance*>> GridActorSetInstances;
        GridActorSetInstances.InsertDefaulted(0, AllGrids.Num());

        StreamingGenerationContext->ForEachActorSetInstance([&GridsMapping, &GridActorSetInstances](const IStreamingGenerationContext::FActorSetInstance& ActorSetInstance)
        {
                int32* FoundIndex = GridsMapping.Find(ActorSetInstance.RuntimeGrid);
                if (!FoundIndex)
                {
                        //@todo_ow should this be done upstream?
                        UE_LOG(LogWorldPartition, Error, TEXT("Invalid partition grid '%s' referenced by actor cluster"), *ActorSetInstance.RuntimeGrid.ToString());
                }

                int32 GridIndex = FoundIndex ? *FoundIndex : 0;
                GridActorSetInstances[GridIndex].Add(&ActorSetInstance);
        });

        const FBox WorldBounds = StreamingGenerationContext->GetWorldBounds();
        for (int32 GridIndex=0; GridIndex < AllGrids.Num(); GridIndex++)
        {
                const FSpatialHashRuntimeGrid& Grid = AllGrids[GridIndex];
                const FSquare2DGridHelper PartionedActors = GetPartitionedActors(WorldBounds, Grid, GridActorSetInstances[GridIndex]);
                if (!CreateStreamingGrid(Grid, PartionedActors, StreamingPolicy, OutPackagesToGenerate))
                {
                        return false;
                }
        }

        return true;
}

UWorldPartitionRuntimeSpatialHash::GenerateStreaming 的主要工作是创建 StreamingGrid,并将其添加到 StreamingGrids 数组中。

世界分区管理者-UWorldPartitionRuntimeSpatialHash

我们仔细看一下UWorldPartitionRuntimeSpatialHash这个Class.
先假设这是世界分区的顶层结构,那么应该在WorldPartitionRuntimeHash(世界分区运行时哈希)中保存所有的分区Grids信息,并且提供操控、组合、遍历、删除这些Grids信息的方法。

    UCLASS(MinimalAPI)
  class UWorldPartitionRuntimeSpatialHash : public UWorldPartitionRuntimeHash
  {
  public:
          ENGINE_API virtual void PreSave(const class ITargetPlatform* TargetPlatform) override;

          virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override;

          ENGINE_API virtual void Serialize(FArchive& Ar) override;


  #if WITH_EDITOR
          ENGINE_API virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;

          ENGINE_API virtual void SetDefaultValues() override;
          virtual bool SupportsHLODs() const override { return true; }
          ENGINE_API virtual void FlushStreaming() override;
          ENGINE_API virtual bool GenerateHLOD(ISourceControlHelper* SourceControlHelper, const IStreamingGenerationContext* StreamingGenerationContext, bool bCreateActorsOnly) const override;
          ENGINE_API virtual bool IsValidGrid(FName GridName) const override;
          ENGINE_API virtual void DrawPreview() const override;

          ENGINE_API virtual URuntimeHashExternalStreamingObjectBase* StoreToExternalStreamingObject(UObject* StreamingObjectOuter, FName StreamingObjectName) override;

          static ENGINE_API FString GetCellNameString(UWorld* InOuterWorld, FName InGridName, const FGridCellCoord& InCellGlobalCoord, const FDataLayersID& InDataLayerID, const FGuid& InContentBundleID, FString* OutInstanceSuffix = nullptr);
          static ENGINE_API FGuid GetCellGuid(FName InGridName, int32 InCellSize, const FGridCellCoord& InCellGlobalCoord, const FDataLayersID& InDataLayerID, const FGuid& InContentBundleID);

          ENGINE_API bool GetPreviewGrids() const;
          ENGINE_API void SetPreviewGrids(bool bInPreviewGrids);

          ENGINE_API int32 GetPreviewGridLevel() const;
          ENGINE_API void SetPreviewGridLevel(int32 InPreviewGridLevel);
  #endif

          // streaming interface
u       // 核心功能:遍历StreamingGrids,并且调用Func对每个Grid进行操作
          ENGINE_API virtual void ForEachStreamingCells(TFunctionRef<bool(const UWorldPartitionRuntimeCell*)> Func) const override;
          ENGINE_API virtual void ForEachStreamingCellsQuery(const FWorldPartitionStreamingQuerySource& QuerySource, TFunctionRef<bool(const UWorldPartitionRuntimeCell*)> Func, FWorldPartitionQueryCache* QueryCache = nullptr) const override;
          ENGINE_API virtual void ForEachStreamingCellsSources(const TArray<FWorldPartitionStreamingSource>& Sources, TFunctionRef<bool(const UWorldPartitionRuntimeCell*, EStreamingSourceTargetState)> Func) const override;
          ENGINE_API virtual uint32 ComputeUpdateStreamingHash() const override;

          ENGINE_API virtual bool InjectExternalStreamingObject(URuntimeHashExternalStreamingObjectBase* ExternalStreamingObject) override;
          ENGINE_API virtual bool RemoveExternalStreamingObject(URuntimeHashExternalStreamingObjectBase* ExternalStreamingObject) override;

          ENGINE_API uint32 GetNumGrids() const;

          static ENGINE_API FString GetCellCoordString(const FGridCellCoord& InCellGlobalCoord);

          ENGINE_API void ForEachStreamingGrid(TFunctionRef<void(const FSpatialHashStreamingGrid&)> Func) const;

  protected:
       // 获取StreamingGrid通过GridName
          ENGINE_API const FSpatialHashStreamingGrid* GetStreamingGridByName(FName InGridName) const;
          ENGINE_API void ForEachStreamingGrid(TFunctionRef<void(FSpatialHashStreamingGrid&)> Func);
          ENGINE_API void ForEachStreamingGridBreakable(TFunctionRef<bool(const FSpatialHashStreamingGrid&)> Func) const;

          ENGINE_API virtual EWorldPartitionStreamingPerformance GetStreamingPerformanceForCell(const UWorldPartitionRuntimeCell* Cell) const override;

  #if WITH_EDITOR
          ENGINE_API virtual bool GenerateStreaming(class UWorldPartitionStreamingPolicy* StreamingPolicy, const IStreamingGenerationContext* StreamingGenerationContext, TArray<FString>* OutPackagesToGenerate = nullptr) override;
          ENGINE_API virtual void DumpStateLog(FHierarchicalLogArchive& Ar) const override;
  #endif

  private:
          /** Console command used to change loading range for a given streaming grid */
          static ENGINE_API class FAutoConsoleCommand OverrideLoadingRangeCommand;

  #if WITH_EDITORONLY_DATA
       // 编辑器模式下,用于存储StreamingGrid
          UPROPERTY(EditAnywhere, Config, Category = RuntimeSettings)
          TArray<FSpatialHashRuntimeGrid> Grids;

          /** Whether to preview runtime grids. */
          UPROPERTY(Transient)
          bool bPreviewGrids;

          UPROPERTY(Transient)
          int32 PreviewGridLevel;

          UPROPERTY(Transient)
          mutable FWorldPartitionRuntimeSpatialHashGridPreviewer GridPreviewer;
  #endif

          /** Disable to help break the pattern caused by world partition promotion of actors to upper grid levels that are always aligned on child levels. */
          UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = RuntimeSettings)
          EWorldPartitionCVarProjectDefaultOverride UseAlignedGridLevels;

          /** Disable to avoid snapping higher levels cells to child cells. Only used when bUseAlignedGridLevels is false. */
          UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = RuntimeSettings)
          EWorldPartitionCVarProjectDefaultOverride SnapNonAlignedGridLevelsToLowerLevels;

          /** Enable to place actors smaller than a cell size into their corresponding cell using their location instead of their bounding box. */
          UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = RuntimeSettings)
          EWorldPartitionCVarProjectDefaultOverride PlaceSmallActorsUsingLocation;

          /** Enable to place partitioned actors into their corresponding cell using their location instead of their bounding box. */
          UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = RuntimeSettings)
          EWorldPartitionCVarProjectDefaultOverride PlacePartitionActorsUsingLocation;

          /** Whether this hash enables Z culling. */
          UPROPERTY(EditAnywhere, Config, Category = RuntimeSettings)
          bool bEnableZCulling;

  protected:
          /** 
           * Represents the streaming grids (PIE or Game)
           */
       // 运行时模式下,用于存储StreamingGrid的容器
          UPROPERTY(NonPIEDuplicateTransient)
          TArray<FSpatialHashStreamingGrid> StreamingGrids;
       // 名字与StreamingGrid的映射Map
          mutable TMap<FName, const FSpatialHashStreamingGrid*> NameToGridMapping;
          mutable bool bIsNameToGridMappingDirty;

  private:
       // 一组绘图接口,绘制StreamingGrid信息
          ENGINE_API virtual bool Draw2D(FWorldPartitionDraw2DContext& DrawContext) const override;
          ENGINE_API virtual void Draw3D(const TArray<FWorldPartitionStreamingSource>& Sources) const override;
          ENGINE_API virtual bool ContainsRuntimeHash(const FString& Name) const override;
          ENGINE_API virtual bool IsStreaming3D() const override;

          ENGINE_API void GetAlwaysLoadedStreamingCells(const FSpatialHashStreamingGrid& StreamingGrid, TSet<const UWorldPartitionRuntimeCell*>& Cells) const;
          ENGINE_API const TMap<FName, const FSpatialHashStreamingGrid*>& GetNameToGridMapping() const;
  #if WITH_EDITOR
          ENGINE_API bool CreateStreamingGrid(const FSpatialHashRuntimeGrid& RuntimeGrid, const FSquare2DGridHelper& PartionedActors, UWorldPartitionStreamingPolicy* StreamingPolicy, TArray<FString>* OutPackagesToGenerate = nullptr);
  #endif
          ENGINE_API TArray<const FSpatialHashStreamingGrid*> GetFilteredStreamingGrids() const;

          friend class UWorldPartitionSubsystem;
  };

UWorldPartitionRuntimeSpatialHash的功能是管理运行时模式下的StreamingGrid,以及运行时模式下的StreamingGrid的绘制、包含、和获取。以及在运行时模式下的遍历调用StreamingGrid的方法。

网格对象(Grid)

如果说UWorldPartitionRuntimeSpatialHash是世界分区的管理者,那么Grid就代表了每一个具体的分区。
我们看一下Grid结构,感叹于虚幻团队的细心,这个结构有两种:编辑器模式下用于调试的FSpatialHashRuntimeGrid结构和运行时模式下负责Grid实际功能的FSpatialHashStreamingGrid结构

用于编辑器下调试的FSpatialHashRuntimeGrid

/**
 * Represents a runtime grid (editing)
 */
USTRUCT()
struct FSpatialHashRuntimeGrid
{

        FSpatialHashRuntimeGrid()
#if WITH_EDITORONLY_DATA
                : CellSize(12800)
                , LoadingRange(25600)
                , bBlockOnSlowStreaming(false)
                , Origin(FVector2D::ZeroVector)
                , Priority(0)
                , DebugColor(FLinearColor::MakeRandomColor())
                , bClientOnlyVisible(false)
                , HLODLayer(nullptr)
#endif
        {}

#if WITH_EDITORONLY_DATA
        UPROPERTY(EditAnywhere, Category=Settings)
        FName GridName;

        UPROPERTY(EditAnywhere, Category=Settings)
        int32 CellSize;

        UPROPERTY(EditAnywhere, Category=Settings)
        float LoadingRange;

        /** Should streaming block in situations where cells aren't getting loaded fast enough. */
        UPROPERTY(EditAnywhere, Category=Settings)
        bool bBlockOnSlowStreaming;

        UPROPERTY(EditAnywhere, Category=Settings)
        FVector2D Origin;

        UPROPERTY(EditAnywhere, Category=Settings)
        int32 Priority;

        UPROPERTY(EditAnywhere, Category=Settings, meta = (IgnoreForMemberInitializationTest))
        FLinearColor DebugColor;

        UPROPERTY()
        bool bClientOnlyVisible;

        UPROPERTY()
        TObjectPtr<const UHLODLayer> HLODLayer;

        bool operator == (const FSpatialHashRuntimeGrid& Other) const;
        bool operator != (const FSpatialHashRuntimeGrid& Other) const;
#endif
};

FSpatialHashRuntimeGrid主要是在编辑模式下使用的,包括GridName、CellSize、LoadingRange、bBlockOnSlowStreaming、Origin、Priority、DebugColor、bClientOnlyVisible、HLODLayer等字段。专为编辑器模式使用的,而且在编辑器模式下是一个可以修改的Grid结构,可以实时修改参数,来快捷看到运行结果。

用于实际运行的FSpatialHashStreamingGrid

/**
 * Represents a PIE/Game streaming grid
 */
USTRUCT()
struct FSpatialHashStreamingGrid
{

        ENGINE_API FSpatialHashStreamingGrid();
        ENGINE_API ~FSpatialHashStreamingGrid();

        // Move constructor.
        FSpatialHashStreamingGrid(const FSpatialHashStreamingGrid& Other) = delete;
        FSpatialHashStreamingGrid(FSpatialHashStreamingGrid&& Other) = default;

        // Move assignment.
        FSpatialHashStreamingGrid& operator=(const FSpatialHashStreamingGrid& Other) = delete;
        FSpatialHashStreamingGrid& operator=(FSpatialHashStreamingGrid&& Other) = default;

        UPROPERTY()
        FName GridName;

        UPROPERTY()
        FVector Origin;

        UPROPERTY()
        int32 CellSize;

        UPROPERTY()
        float LoadingRange;

        UPROPERTY()
        bool bBlockOnSlowStreaming;

        UPROPERTY()
        FLinearColor DebugColor;

        UPROPERTY()
        TArray<FSpatialHashStreamingGridLevel> GridLevels;

        UPROPERTY()
        FBox WorldBounds;

        UPROPERTY()
        bool bClientOnlyVisible;

        UPROPERTY()
        TObjectPtr<const UHLODLayer> HLODLayer;

        float OverrideLoadingRange;

        float GetLoadingRange() const
        {
                if (OverrideLoadingRange >= 0.f)
                {
                        return OverrideLoadingRange;
                }
                return LoadingRange;
        }

        ENGINE_API void InjectExternalStreamingObjectGrid(const FSpatialHashStreamingGrid& InExternalObjectStreamingGrid) const;
        ENGINE_API void RemoveExternalStreamingObjectGrid(const FSpatialHashStreamingGrid& InExternalObjectStreamingGrid) const;

        ENGINE_API bool InsertGridCell(UWorldPartitionRuntimeCell* InGridCell, const FGridCellCoord& InGridCellCoords);

        // Used by PIE/Game
        ENGINE_API int64 GetCellSize(int32 Level) const;
        ENGINE_API void GetCells(const FWorldPartitionStreamingQuerySource& QuerySource, TSet<const UWorldPartitionRuntimeCell*>& OutCells, bool bEnableZCulling, FWorldPartitionQueryCache* QueryCache = nullptr) const;
        ENGINE_API void GetCells(const TArray<FWorldPartitionStreamingSource>& Sources, UWorldPartitionRuntimeHash::FStreamingSourceCells& OutActivateCells, UWorldPartitionRuntimeHash::FStreamingSourceCells& OutLoadCells, bool bEnableZCulling) const;
        ENGINE_API void GetNonSpatiallyLoadedCells(TSet<const UWorldPartitionRuntimeCell*>& OutActivateCells, TSet<const UWorldPartitionRuntimeCell*>& OutLoadCells) const;
        ENGINE_API void Draw2D(const class UWorldPartitionRuntimeSpatialHash* Owner, const FBox2D& Region2D, const FBox2D& GridScreenBounds, TFunctionRef<FVector2D(const FVector2D&, bool)> WorldToScreen, FWorldPartitionDraw2DContext& DrawContext) const;
        ENGINE_API void Draw3D(const class UWorldPartitionRuntimeSpatialHash* Owner, const TArray<FWorldPartitionStreamingSource>& Sources, const FTransform& Transform) const;
        ENGINE_API void ForEachRuntimeCell(TFunctionRef<bool(const UWorldPartitionRuntimeCell*)> Func) const;
        ENGINE_API const FSquare2DGridHelper& GetGridHelper() const;

private:
        ENGINE_API void ForEachRuntimeCell(const FGridCellCoord& Coords, TFunctionRef<void(const UWorldPartitionRuntimeCell*)> Func) const;
        ENGINE_API void ForEachLayerCell(const FGridCellCoord& Coords, TFunctionRef<void(const FSpatialHashStreamingGridLayerCell*)> Func) const;
        ENGINE_API void DrawStreamingSource2D(const FBox2D& GridScreenBounds, const FSphericalSector& Shape, TFunctionRef<FVector2D(const FVector2D&)> WorldToScreen, const FColor& Color, FWorldPartitionDraw2DContext& DrawContext) const;
        ENGINE_API void DrawStreamingSource3D(UWorld* World, const FSphericalSector& Shape, const FTransform& Transform, const FColor& Color) const;
        ENGINE_API void GetFilteredCellsForDebugDraw(const FSpatialHashStreamingGridLayerCell* LayerCell, TArray<const UWorldPartitionRuntimeCell*>& FilteredCells) const;
        ENGINE_API EWorldPartitionRuntimeCellVisualizeMode GetStreamingCellVisualizeMode() const;
        mutable FSquare2DGridHelper* GridHelper;

        // Contains cells injected at runtime from content bundles
        mutable TArray<FSpatialHashStreamingGridLevel> InjectedGridLevels;
};

FSpatialHashStreamingGrid是一个网格节点。一个世界分区是由很多的网格节点(Grid)组成的。这些网格节点(Grid)通过WorldPartitionRuntimeHash组织在一起,动态的加载和卸载、并且分布在不同的层级(Layer),最终构成了一个无缝运行的世界分区。
我们仔细看一下网格节点做了哪些事情:
1 每个网格都有GridName(网格名字)、Origin(原点)、size(尺寸)、LayerCount(层数)、WorldBounds(世界边界)、LoadingRange(加载范围)、DebugColor(调试颜色)等属性。正是因为每个Grid都有自己的世界边界,尺寸以及加载范围,所以每个Grid都可以是任意不规则的形状。不要看到字面上的Grid网格就以为是方格子。
2 每个网格都是一个多层结构,多层结构存储在GridLevels这个TArray中。这个容易理解,无论是2D游戏还是3D游戏,都是通过这种多层结构来实现的场景。
3 提供了网格操作常用的API接口。

最后

到这里,我们对虚幻世界分区的基本结构有了直观了解,后面会继续展开细节,并做一些测试。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值