WorldPartition->RuntimeHash->ForEachStreamingCellsSources(StreamingSources, [this](const UWorldPartitionRuntimeCell* Cell, EStreamingSourceTargetState TargetState)
{
switch (TargetState)
{
case EStreamingSourceTargetState::Loaded:
FrameLoadCells.Add(Cell);
break;
case EStreamingSourceTargetState::Activated:
FrameActivateCells.Add(Cell);
break;
default:
check(0);
}
return true;
});
void UWorldPartitionRuntimeSpatialHash::ForEachStreamingCellsSources(const TArray<FWorldPartitionStreamingSource>& Sources, TFunctionRef<bool(const UWorldPartitionRuntimeCell*, EStreamingSourceTargetState)> Func) const
{
//獲得管理datalayer的subsystem
const UDataLayerSubsystem* DataLayerSubsystem = GetWorld()->GetSubsystem<UDataLayerSubsystem>();
FStreamingSourceCells ActivateStreamingSourceCells;
FStreamingSourceCells LoadStreamingSourceCells;
//如果沒有source,就要處理always load的 cell
if (Sources.Num() == 0)
{
// Get always loaded cells
ForEachStreamingGrid([&] (const FSpatialHashStreamingGrid& StreamingGrid)
{
if (IsCellRelevantFor(StreamingGrid.bClientOnlyVisible))
{
StreamingGrid.GetAlwaysLoadedCells(DataLayerSubsystem, ActivateStreamingSourceCells.GetCells(), LoadStreamingSourceCells.GetCells());
}
});
}
else
{
// Get cells based on streaming sources
ForEachStreamingGrid([&](const FSpatialHashStreamingGrid& StreamingGrid)
{
if (IsCellRelevantFor(StreamingGrid.bClientOnlyVisible))
{
//根據sources處理cell
StreamingGrid.GetCells(Sources, DataLayerSubsystem, ActivateStreamingSourceCells, LoadStreamingSourceCells, GetEffectiveEnableZCulling(bEnableZCulling));
}
});
}
for (const UWorldPartitionRuntimeCell* Cell : ActivateStreamingSourceCells.GetCells())
{
if (!Func(Cell, EStreamingSourceTargetState::Activated))
{
return;
}
}
for (const UWorldPartitionRuntimeCell* Cell : LoadStreamingSourceCells.GetCells())
{
if (!Func(Cell, EStreamingSourceTargetState::Loaded))
{
return;
}
}
}
//FSpatialHashStreamingGrid---->FSpatialHashStreamingGridLevel---->FSpatialHashStreamingGridLayerCell------>UWorldPartitionRuntimeSpatialHashCell
//有個疑問cell 也有datalayer屬性,不應該只有actor才有datalayer嗎
void FSpatialHashStreamingGrid::GetAlwaysLoadedCells(const UDataLayerSubsystem* DataLayerSubsystem, TSet<const UWorldPartitionRuntimeCell*>& OutActivateCells, TSet<const UWorldPartitionRuntimeCell*>& OutLoadCells) const
{
//這個grid上有很多個grid level
if (GridLevels.Num() > 0)
{
const int32 TopLevel = GridLevels.Num() - 1;
for (const FSpatialHashStreamingGridLayerCell& LayerCell : GridLevels[TopLevel].LayerCells)
{
for (const UWorldPartitionRuntimeCell* Cell : LayerCell.GridCells)
{
//要麽這個cell沒有datalayer,要麽有,其中有一個datalayer是active狀態
if (!Cell->HasDataLayers() || (DataLayerSubsystem && DataLayerSubsystem->IsAnyDataLayerInEffectiveRuntimeState(Cell->GetDataLayers(), EDataLayerRuntimeState::Activated)))
{
check(Cell->IsAlwaysLoaded() || Cell->HasDataLayers() || Cell->GetContentBundleID().IsValid());
OutActivateCells.Add(Cell);
}
else if(DataLayerSubsystem && DataLayerSubsystem->IsAnyDataLayerInEffectiveRuntimeState(Cell->GetDataLayers(), EDataLayerRuntimeState::Loaded))
{
check(Cell->HasDataLayers());
OutLoadCells.Add(Cell);
}
}
}
}
}
void FSpatialHashStreamingGrid::GetCells(const TArray<FWorldPartitionStreamingSource>& Sources, const UDataLayerSubsystem* DataLayerSubsystem, UWorldPartitionRuntimeHash::FStreamingSourceCells& OutActivateCells, UWorldPartitionRuntimeHash::FStreamingSourceCells& OutLoadCells, bool bEnableZCulling) const
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSpatialHashStreamingGrid::GetCells);
struct FStreamingSourceInfo
{
FStreamingSourceInfo(const FWorldPartitionStreamingSource& InSource, const FSphericalSector& InSourceShape)
: Source(InSource)
, SourceShape(InSourceShape)
{}
const FWorldPartitionStreamingSource& Source;
const FSphericalSector& SourceShape;
};
typedef TMap<FGridCellCoord, TArray<FStreamingSourceInfo>> FIntersectingCells;
FIntersectingCells AllActivatedCells;
const float GridLoadingRange = GetLoadingRange();
const FSquare2DGridHelper& Helper = GetGridHelper();
for (const FWorldPartitionStreamingSource& Source : Sources)
{
Source.ForEachShape(GridLoadingRange, GridName, HLODLayer, /*bProjectIn2D*/ true, [&](const FSphericalSector& Shape)
{
FStreamingSourceInfo Info(Source, Shape);
Helper.ForEachIntersectingCells(Shape, [&](const FGridCellCoord& Coords)
{
bool bAddedActivatedCell = false;
#if !UE_BUILD_SHIPPING
if ((GFilterRuntimeSpatialHashGridLevel == INDEX_NONE) || (GFilterRuntimeSpatialHashGridLevel == Coords.Z))
#endif
{
if (const FSpatialHashStreamingGridLayerCell* LayerCell = GetLayerCell(Coords))
{
for (const UWorldPartitionRuntimeCell* Cell : LayerCell->GridCells)
{
if (!bEnableZCulling || TRange<double>(Cell->GetMinMaxZ().X, Cell->GetMinMaxZ().Y).Overlaps(TRange<double>(Shape.GetCenter().Z - Shape.GetRadius(), Shape.GetCenter().Z + Shape.GetRadius())))
{
if (!Cell->HasDataLayers() || (DataLayerSubsystem && DataLayerSubsystem->IsAnyDataLayerInEffectiveRuntimeState(Cell->GetDataLayers(), EDataLayerRuntimeState::Activated)))
{
if (Source.TargetState == EStreamingSourceTargetState::Loaded)
{
OutLoadCells.AddCell(Cell, Source, Shape);
}
else
{
check(Source.TargetState == EStreamingSourceTargetState::Activated);
OutActivateCells.AddCell(Cell, Source, Shape);
bAddedActivatedCell = !GRuntimeSpatialHashUseAlignedGridLevels && GRuntimeSpatialHashSnapNonAlignedGridLevelsToLowerLevels;
}
}
else if (DataLayerSubsystem && DataLayerSubsystem->IsAnyDataLayerInEffectiveRuntimeState(Cell->GetDataLayers(), EDataLayerRuntimeState::Loaded))
{
OutLoadCells.AddCell(Cell, Source, Shape);
}
}
}
}
}
if (bAddedActivatedCell)
{
AllActivatedCells.FindOrAdd(Coords).Add(Info);
}
});
});
}
GetAlwaysLoadedCells(DataLayerSubsystem, OutActivateCells.GetCells(), OutLoadCells.GetCells());
if (!GRuntimeSpatialHashUseAlignedGridLevels && GRuntimeSpatialHashSnapNonAlignedGridLevelsToLowerLevels)
{
auto FindIntersectingParents = [&Helper, this](const FIntersectingCells& InAllCells, const FIntersectingCells& InTestCells, FIntersectingCells& OutIntersectingCells)
{
bool bFound = false;
const int32 AlwaysLoadedLevel = Helper.Levels.Num() - 1;
for (const auto& InTestCell : InTestCells)
{
const FGridCellCoord& TestCell = InTestCell.Key;
int32 CurrentLevelIndex = TestCell.Z;
int32 ParentLevelIndex = CurrentLevelIndex + 1;
// Only test with Parent Level if it's below the AlwaysLoaded Level
if (ParentLevelIndex < AlwaysLoadedLevel)
{
FBox2D CurrentLevelCellBounds;
Helper.Levels[CurrentLevelIndex].GetCellBounds(FGridCellCoord2(TestCell.X, TestCell.Y), CurrentLevelCellBounds);
FBox Box(FVector(CurrentLevelCellBounds.Min, 0), FVector(CurrentLevelCellBounds.Max, 0));
Helper.ForEachIntersectingCells(Box, [&](const FGridCellCoord& IntersectingCoords)
{
check(IntersectingCoords.Z >= ParentLevelIndex);
if (!InAllCells.Contains(IntersectingCoords))
{
if (!OutIntersectingCells.Contains(IntersectingCoords))
{
OutIntersectingCells.Add(IntersectingCoords, InTestCell.Value);
bFound = true;
}
}
}, ParentLevelIndex);
}
}
return bFound;
};
FIntersectingCells AllParentCells;
FIntersectingCells TestCells = AllActivatedCells;
FIntersectingCells IntersectingCells;
bool bFound = false;
do
{
bFound = FindIntersectingParents(AllActivatedCells, TestCells, IntersectingCells);
if (bFound)
{
AllActivatedCells.Append(IntersectingCells);
AllParentCells.Append(IntersectingCells);
TestCells = MoveTemp(IntersectingCells);
check(IntersectingCells.IsEmpty());
}
} while (bFound);
for (const auto& ParentCell : AllParentCells)
{
if (const FSpatialHashStreamingGridLayerCell* LayerCell = GetLayerCell(ParentCell.Key))
{
for (const UWorldPartitionRuntimeCell* Cell : LayerCell->GridCells)
{
if (!Cell->HasDataLayers() || (DataLayerSubsystem && DataLayerSubsystem->IsAnyDataLayerInEffectiveRuntimeState(Cell->GetDataLayers(), EDataLayerRuntimeState::Activated)))
{
for (const auto& Info : ParentCell.Value)
{
OutActivateCells.AddCell(Cell, Info.Source, Info.SourceShape);
}
}
}
}
}
}
}
---------------------------------------------------------------------------------------------------
源碼分析 ue5.1
WorldPartition初始化
void ULevel::OnLevelLoaded()
{
#if WITH_EDITOR
FixupActorFolders();
#endif
auto IsValidLevelInstanceWorldPartition = [](UWorldPartition* InWorldPartition)
{
#if WITH_EDITOR
return InWorldPartition->CanBeUsedByLevelInstance();
#else
return false;
#endif
};
// 1. Cook commandlet does it's own UWorldPartition::Initialize call in FWorldPartitionCookPackageSplitter::GetGenerateList
// 2. Do not Initialize if World doesn't have a UWorldPartitionSubsystem (Known case is when WorldType == EWorldType::Inactive)
if (!IsRunningCookCommandlet() && OwningWorld->HasSubsystem<UWorldPartitionSubsystem>())
{
if (UWorldPartition* WorldPartition = GetWorldPartition())
{
//
// When do we need to initialize the associated world partition object?
//
// - When the level is the main world persistent level
// - When the sublevel is streamed in the editor (mainly for data layers)
// - When the sublevel is streamed in game and the main world is not partitioned
//
const bool bIsOwningWorldGameWorld = OwningWorld->IsGameWorld();
const bool bIsOwningWorldPartitioned = OwningWorld->IsPartitionedWorld();
const bool bIsValidLevelInstance = IsValidLevelInstanceWorldPartition(WorldPartition);
const bool bIsMainWorldLevel = OwningWorld->PersistentLevel == this;
const bool bInitializeForEditor = !bIsOwningWorldGameWorld && bIsValidLevelInstance;
const bool bInitializeForGame = bIsOwningWorldGameWorld && !bIsOwningWorldPartitioned;
UE_LOG(LogWorldPartition, Log, TEXT("ULevel::OnLevelLoaded(%s)(bIsOwningWorldGameWorld=%d, bIsOwningWorldPartitioned=%d, bIsValidLevelInstance=%d, InitializeForMainWorld=%d, InitializeForEditor=%d, InitializeForGame=%d)"),
*GetTypedOuter<UWorld>()->GetName(), bIsOwningWorldGameWorld ? 1 : 0, bIsOwningWorldPartitioned ? 1 : 0, bIsValidLevelInstance ? 1 : 0, bIsMainWorldLevel ? 1 : 0, bInitializeForEditor ? 1 : 0, bInitializeForGame ? 1 : 0);
if (bIsMainWorldLevel || bInitializeForEditor || bInitializeForGame)
{
FTransform Transform = FTransform::Identity;
if (ULevelStreaming* LevelStreaming = FLevelUtils::FindStreamingLevel(this))
{
Transform = LevelStreaming->LevelTransform;
}
// It is allowed for a WorldPartition to already be initialized in the case where a partitioned sub-level
// was streamed-out and is streamed-in again, without a CleanupLevel (GC).
if (!WorldPartition->IsInitialized())
{
//走到這裏
WorldPartition->Initialize(OwningWorld, Transform);
}
else
{
check(OwningWorld->IsGameWorld());
check(WorldPartition->GetInstanceTransform().Equals(Transform));
}
}
}
}
}
UWorldPartition* ULevel::GetWorldPartition() const
{
return bIsPartitioned ? GetWorldSettings()->GetWorldPartition() : nullptr;
}
在WorldPartition->Initialize的最後一行調用
FWorldPartitionEvents::BroadcastWorldPartitionInitialized(World, this);
調用之後UWorldPartitionSubsystem中RegisteredWorldPartitions就有值了
第一次更新
void UEngine::BlockTillLevelStreamingCompleted(UWorld* InWorld)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UEngine_BlockTillLevelStreamingCompleted);
check(InWorld);
InWorld->BlockTillLevelStreamingCompleted();
}
void UWorld::InternalUpdateStreamingState()
{
// Update streaming levels state using streaming volumes.
// Issues level streaming load/unload requests based on local players being inside/outside level streaming volumes.
ProcessLevelStreamingVolumes();
// Update WorldComposition required streaming levels
if (WorldComposition)
{
WorldComposition->UpdateStreamingState();
}
// Update World Subsystems required streaming levels
const TArray<UWorldSubsystem*>& WorldSubsystems = SubsystemCollection.GetSubsystemArray<UWorldSubsystem>(UWorldSubsystem::StaticClass());
for (UWorldSubsystem* WorldSubsystem : WorldSubsystems)
{
WorldSubsystem->UpdateStreamingState();
}
}
然後每幀update的堆棧如下
void UWorldPartitionSubsystem::UpdateStreamingState()
{
for (UWorldPartition* RegisteredWorldPartition : RegisteredWorldPartitions)
{
RegisteredWorldPartition->UpdateStreamingState();
}
}
void UWorldPartition::UpdateStreamingState()
{
if (GetWorld()->IsGameWorld())
{
StreamingPolicy->UpdateStreamingState();
}
}
每幀更新的邏輯
void UWorldPartitionStreamingPolicy::UpdateStreamingState()
{
TRACE_CPUPROFILER_EVENT_SCOPE(UWorldPartitionStreamingPolicy::UpdateStreamingState);
UWorld* World = GetWorld();
check(World);
check(World->IsGameWorld());
// Dermine if the World's BlockTillLevelStreamingCompleted was triggered by WorldPartitionStreamingPolicy
if (bCriticalPerformanceRequestedBlockTillOnWorld && IsInBlockTillLevelStreamingCompleted())
{
bCriticalPerformanceRequestedBlockTillOnWorld = false;
CriticalPerformanceBlockTillLevelStreamingCompletedEpoch = World->GetBlockTillLevelStreamingCompletedEpoch();
}
// Update streaming sources
UpdateStreamingSources();
TSet<FName> ClientVisibleLevelNames;
bool bUpdateEpoch = false;
check(FrameActivateCells.IsEmpty());
check(FrameLoadCells.IsEmpty());
ON_SCOPE_EXIT
{
// Reset frame StreamingSourceCells (optimization to avoid reallocation at every call to UpdateStreamingState)
FrameActivateCells.Reset();
FrameLoadCells.Reset();
};
const bool bIsServer = WorldPartition->IsServer();
const bool bIsServerStreamingEnabled = WorldPartition->IsServerStreamingEnabled();
const bool bCanDeactivateOrUnloadCells = !bIsServer || WorldPartition->IsServerStreamingOutEnabled();
const int32 NewServerStreamingEnabledEpoch = bIsServer ? (bIsServerStreamingEnabled ? 1 : 0) : INT_MIN;
if (!bIsServer || bIsServerStreamingEnabled || AWorldPartitionReplay::IsPlaybackEnabled(World))
{
// When world partition can't stream, all cells must be unloaded
if (WorldPartition->CanStream() && WorldPartition->RuntimeHash)
{
UWorldPartitionRuntimeCell::DirtyStreamingSourceCacheEpoch();
WorldPartition->RuntimeHash->ForEachStreamingCellsSources(StreamingSources, [this](const UWorldPartitionRuntimeCell* Cell, EStreamingSourceTargetState TargetState)
{
switch (TargetState)
{
case EStreamingSourceTargetState::Loaded:
FrameLoadCells.Add(Cell);
break;
case EStreamingSourceTargetState::Activated:
FrameActivateCells.Add(Cell);
break;
default:
check(0);
}
return true;
});
}
}
if (bIsServer)
{
// Server will activate all non data layer cells at first and then load/activate/unload data layer cells only when the data layer states change
if (!bIsServerStreamingEnabled &&
(ServerStreamingEnabledEpoch == NewServerStreamingEnabledEpoch) &&
(ContentBundleServerEpoch == FContentBundle::GetContentBundleEpoch()) &&
(DataLayersStatesServerEpoch == AWorldDataLayers::GetDataLayersStateEpoch()))
{
// Server as nothing to do early out
return;
}
bUpdateEpoch = true;
// Gather Client visible level names
if (const UNetDriver* NetDriver = World->GetNetDriver())
{
for (UNetConnection* Connection : NetDriver->ClientConnections)
{
ClientVisibleLevelNames.Add(Connection->GetClientWorldPackageName());
ClientVisibleLevelNames.Append(Connection->ClientVisibleLevelNames);
ClientVisibleLevelNames.Append(Connection->GetClientMakingVisibleLevelNames());
}
}
const UDataLayerSubsystem* DataLayerSubsystem = GetWorld()->GetSubsystem<UDataLayerSubsystem>();
TSet<FName> EffectiveActiveDataLayerNames = DataLayerSubsystem->GetEffectiveActiveDataLayerNames();
TSet<FName> EffectiveLoadedDataLayerNames = DataLayerSubsystem->GetEffectiveLoadedDataLayerNames();
auto AddServerFrameCells = [this, &EffectiveLoadedDataLayerNames, &EffectiveActiveDataLayerNames](const UWorldPartitionRuntimeCell* Cell)
{
// Non Data Layer Cells + Active Data Layers
if (!Cell->HasDataLayers() || Cell->HasAnyDataLayer(EffectiveActiveDataLayerNames))
{
FrameActivateCells.Add(Cell);
}
// Loaded Data Layers Cells only
if (EffectiveLoadedDataLayerNames.Num())
{
if (Cell->HasDataLayers() && Cell->HasAnyDataLayer(EffectiveLoadedDataLayerNames))
{
FrameLoadCells.Add(Cell);
}
}
};
if (!bIsServerStreamingEnabled)
{
if (WorldPartition->RuntimeHash)
{
WorldPartition->RuntimeHash->ForEachStreamingCells([this, &AddServerFrameCells](const UWorldPartitionRuntimeCell* Cell)
{
AddServerFrameCells(Cell);
return true;
});
}
}
else if (!bCanDeactivateOrUnloadCells)
{
// When server streaming-out is disabled, revisit existing loaded/activated cells and add them in the proper FrameLoadCells/FrameActivateCells
for (const UWorldPartitionRuntimeCell* Cell : ActivatedCells)
{
AddServerFrameCells(Cell);
}
for (const UWorldPartitionRuntimeCell* Cell : LoadedCells)
{
AddServerFrameCells(Cell);
}
}
}
auto ShouldWaitForClientVisibility = [bIsServer, &ClientVisibleLevelNames, &bUpdateEpoch](const UWorldPartitionRuntimeCell* Cell)
{
check(bIsServer);
if (ULevel* Level = Cell->GetLevel())
{
if (ClientVisibleLevelNames.Contains(Cell->GetLevel()->GetPackage()->GetFName()))
{
UE_CLOG(bUpdateEpoch, LogWorldPartition, Verbose, TEXT("Server epoch update delayed by client visibility"));
bUpdateEpoch = false;
return true;
}
}
return false;
};
auto ShouldSkipDisabledHLODCell = [](const UWorldPartitionRuntimeCell* Cell)
{
return Cell->GetIsHLOD() && !UHLODSubsystem::IsHLODEnabled();
};
// Process cells to activate
auto ProcessCellsToActivate = [this, &ShouldSkipDisabledHLODCell](TSet<const UWorldPartitionRuntimeCell*>& Cells)
{
for (TSet<const UWorldPartitionRuntimeCell*>::TIterator It = Cells.CreateIterator(); It; ++It)
{
const UWorldPartitionRuntimeCell* Cell = *It;
if (ShouldSkipCellForPerformance(Cell) || ShouldSkipDisabledHLODCell(Cell))
{
It.RemoveCurrent();
}
else
{
Cell->MergeStreamingSourceInfo();
}
}
};
// Process cells to load
auto ProcessCellsToLoad = [this, bIsServer, &ShouldWaitForClientVisibility, &ShouldSkipDisabledHLODCell](TSet<const UWorldPartitionRuntimeCell*>& Cells)
{
for (TSet<const UWorldPartitionRuntimeCell*>::TIterator It = Cells.CreateIterator(); It; ++It)
{
const UWorldPartitionRuntimeCell* Cell = *It;
// Only deactivated server cells need to call ShouldWaitForClientVisibility (those part of ActivatedCells)
const bool bIsServerCellToDeactivate = bIsServer && ActivatedCells.Contains(Cell);
const bool bShoudSkipServerCell = bIsServerCellToDeactivate && ShouldWaitForClientVisibility(Cell);
if (bShoudSkipServerCell || ShouldSkipCellForPerformance(Cell) || ShouldSkipDisabledHLODCell(Cell))
{
It.RemoveCurrent();
}
else
{
Cell->MergeStreamingSourceInfo();
}
}
};
// Process cells to unload
auto ProcessCellsToUnload = [this, bIsServer, &ShouldWaitForClientVisibility](TSet<const UWorldPartitionRuntimeCell*>& Cells)
{
// Only loop for server as ShouldWaitForClientVisibility only concerns server
if (bIsServer)
{
for (TSet<const UWorldPartitionRuntimeCell*>::TIterator It = Cells.CreateIterator(); It; ++It)
{
const UWorldPartitionRuntimeCell* Cell = *It;
if (ShouldWaitForClientVisibility(Cell))
{
It.RemoveCurrent();
}
}
}
};
// Activation superseeds Loading
FrameLoadCells = FrameLoadCells.Difference(FrameActivateCells);
// Sort cells by importance
auto SortStreamingCells = [this](const TArray<FWorldPartitionStreamingSource>& Sources, const TSet<const UWorldPartitionRuntimeCell*>& InCells, TArray<const UWorldPartitionRuntimeCell*>& OutCells)
{
OutCells.Empty(InCells.Num());
SortStreamingCellsByImportance(InCells, OutCells);
};
// Determine cells to activate (sorted by importance)
TArray<const UWorldPartitionRuntimeCell*> ToActivateCells;
{
TSet<const UWorldPartitionRuntimeCell*> ToActivateCellsUnsorted = FrameActivateCells.Difference(ActivatedCells);
ProcessCellsToActivate(ToActivateCellsUnsorted);
SortStreamingCells(StreamingSources, ToActivateCellsUnsorted, ToActivateCells);
}
// Determine cells to load (sorted by importance)
TArray<const UWorldPartitionRuntimeCell*> ToLoadCells;
{
TSet<const UWorldPartitionRuntimeCell*> ToLoadCellsUnsorted = FrameLoadCells.Difference(LoadedCells);
ProcessCellsToLoad(ToLoadCellsUnsorted);
SortStreamingCells(StreamingSources, ToLoadCellsUnsorted, ToLoadCells);
}
// Determine cells to unload
TArray<const UWorldPartitionRuntimeCell*> ToUnloadCells;
{
TSet<const UWorldPartitionRuntimeCell*> ToUnloadCellsUnsorted = ActivatedCells.Union(LoadedCells).Difference(FrameActivateCells.Union(FrameLoadCells));
ProcessCellsToUnload(ToUnloadCellsUnsorted);
ToUnloadCells = ToUnloadCellsUnsorted.Array();
}
if(World->bMatchStarted)
{
WORLDPARTITION_LOG_UPDATESTREAMINGSTATE(Verbose);
}
else
{
WORLDPARTITION_LOG_UPDATESTREAMINGSTATE(Log);
}
#if !UE_BUILD_SHIPPING
UpdateDebugCellsStreamingPriority(FrameActivateCells, FrameLoadCells);
#endif
if (ToUnloadCells.Num() > 0)
{
SetCellsStateToUnloaded(ToUnloadCells);
}
// Do Activation State first as it is higher prio than Load State (if we have a limited number of loading cells per frame)
if (ToActivateCells.Num() > 0)
{
SetCellsStateToActivated(ToActivateCells);
}
if (ToLoadCells.Num() > 0)
{
SetCellsStateToLoaded(ToLoadCells);
}
// Sort cells and update streaming priority
{
TRACE_CPUPROFILER_EVENT_SCOPE(UWorldPartitionStreamingPolicy::SortCellsAndUpdateStreamingPriority);
TSet<const UWorldPartitionRuntimeCell*> AddToWorldCells;
for (const UWorldPartitionRuntimeCell* ActivatedCell : ActivatedCells)
{
if (!ActivatedCell->IsAddedToWorld() && !ActivatedCell->IsAlwaysLoaded())
{
AddToWorldCells.Add(ActivatedCell);
}
}
SortStreamingCells(StreamingSources, AddToWorldCells, SortedAddToWorldCells);
// Update level streaming priority so that UWorld::UpdateLevelStreaming will naturally process the levels in the correct order
const int32 MaxPrio = SortedAddToWorldCells.Num();
int32 Prio = MaxPrio;
const ULevel* CurrentPendingVisibility = GetWorld()->GetCurrentLevelPendingVisibility();
for (const UWorldPartitionRuntimeCell* Cell : SortedAddToWorldCells)
{
// Current pending visibility level is the most important
const bool bIsCellPendingVisibility = CurrentPendingVisibility && (Cell->GetLevel() == CurrentPendingVisibility);
const int32 SortedPriority = bIsCellPendingVisibility ? MaxPrio + 1 : Prio--;
Cell->SetStreamingPriority(SortedPriority);
}
}
// Evaluate streaming performance based on cells that should be activated
UpdateStreamingPerformance(FrameActivateCells);
// Update Epoch if we aren't waiting for clients anymore
if (bUpdateEpoch)
{
DataLayersStatesServerEpoch = AWorldDataLayers::GetDataLayersStateEpoch();
ContentBundleServerEpoch = FContentBundle::GetContentBundleEpoch();
ServerStreamingEnabledEpoch = NewServerStreamingEnabledEpoch;
UE_LOG(LogWorldPartition, Verbose, TEXT("Server epoch updated"));
}
}
隨著StreamingSource位置的變化,要每幀都對所有的cell相對於source的遠近排序。增加命中率。
void UWorldPartitionRuntimeSpatialHashCell::MergeStreamingSourceInfo() const
{
Super::MergeStreamingSourceInfo();
int32 Count = CachedSourceModulatedDistances.Num();
check(Count == CachedSourcePriorityWeights.Num());
if (Count)
{
double TotalSourcePriorityWeight = 0.f;
for (int32 i = 0; i < Count; ++i)
{
TotalSourcePriorityWeight += CachedSourcePriorityWeights[i];
}
int32 HighestPrioMinDistIndex = 0;
double WeightedModulatedDistance = 0.f;
for (int32 i = 0; i < Count; ++i)
{
WeightedModulatedDistance += CachedSourceModulatedDistances[i] * CachedSourcePriorityWeights[i] / TotalSourcePriorityWeight;
// Find highest priority source with the minimum modulated distance
if ((i != 0) &&
(CachedSourceModulatedDistances[i] < CachedSourceModulatedDistances[HighestPrioMinDistIndex]) &&
(CachedSourcePriorityWeights[i] >= CachedSourcePriorityWeights[HighestPrioMinDistIndex]))
{
HighestPrioMinDistIndex = i;
}
}
// Sorting distance is the minimum between these:
// - the highest priority source with the minimum modulated distance
// - the weighted modulated distance
CachedSourceSortingDistance = FMath::Min(CachedSourceModulatedDistances[HighestPrioMinDistIndex], WeightedModulatedDistance);
}
}
-----------------------------------------------------------------------------------------
darta layer 分editor和 runtime 兩種
editor的datalayer方便場景美術組織和管理場景中的actor 比如可以詳細分類植被 岩石 燈光等
runtime的datalayer一般都服務于gameplay邏輯
--------------------------------------------------------------------------------------
一个actor在 __ExternalActors__中的文件名字 是怎么来的?
FString ULevel::GetActorPackageName(UPackage* InLevelPackage, const FString& InActorPath)
{
// Convert the actor path to lowercase to make sure we get the same hash for case insensitive file systems
FString ActorPath = InActorPath.ToLower();
FArchiveMD5 ArMD5;
ArMD5 << ActorPath;
FMD5Hash MD5Hash;
ArMD5.GetHash(MD5Hash);
FGuid PackageGuid;
check(MD5Hash.GetSize() == sizeof(FGuid));
FMemory::Memcpy(&PackageGuid, MD5Hash.GetBytes(), sizeof(FGuid));
check(PackageGuid.IsValid());
FString GuidBase36 = PackageGuid.ToString(EGuidFormats::Base36Encoded);
check(GuidBase36.Len());
FString BaseDir = GetExternalActorsPath(InLevelPackage);
TStringBuilderWithBuffer<TCHAR, NAME_SIZE> ActorPackageName;
ActorPackageName.Append(BaseDir);
ActorPackageName.Append(TEXT("/"));
ActorPackageName.Append(*GuidBase36, 2);
ActorPackageName.Append(TEXT("/"));
ActorPackageName.Append(*GuidBase36 + 2, 2);
ActorPackageName.Append(TEXT("/"));
ActorPackageName.Append(*GuidBase36 + 4);
return ActorPackageName.ToString();
}
----------------------------------------------------------------------------------
通过世界大纲 选择项
const TArray<class FLevelEditorViewportClient*>& LevelViewportClient = GEditor->GetLevelViewportClients();
for (auto& item : LevelViewportClient)
{
ILevelEditor* LevelEditor = item->ParentLevelEditor.Pin().Get();
if (LevelEditor)
{
TSharedPtr<ISceneOutliner> SceneOutliner = LevelEditor->GetSceneOutliner();
TArray<FSceneOutlinerTreeItemPtr> SelectItems;
SceneOutliner->GetTree().GetSelectedItems(SelectItems);
UE_LOG(LogTemp, Log, TEXT("LLLLLLYYYYYYYXXXXXXX GetSelectedActorCount: %d"), SelectItems.Num());
for (auto& SItem:SelectItems)
{
ISceneOutlinerTreeItem* ISOutLinnerTreeItem = SItem.Get();
if (ISOutLinnerTreeItem->IsA<FActorDescTreeItem>())
{
void UEditorToolBPLibrary::GotPartitionActorName(const TArray<AActor*>& ActorsArray, TMap<FString, FString>& NamesMap)
{
for (auto MyActor : ActorsArray)
{
if(MyActor)
{
UWorld* MyWorld = MyActor->GetWorld();
if (MyWorld)
{
UWorldPartition* MyWorldPartition = MyWorld->GetWorldPartition();
if (MyWorldPartition)
{
UE_LOG(LogTemp, Log, TEXT("ddddddddddddd: %d"), MyWorldPartition->GetActorDescCount());
FWorldPartitionActorDesc* dec = MyWorldPartition->GetActorDesc(MyActor->GetActorGuid());
if (dec)
{
FString DisplayName = dec->GetActorLabel().ToString();
if (NamesMap.Contains(DisplayName))
{
UE_LOG(LogTemp, Log, TEXT("LLLLLLYYYYYYYXXXXXXX repeat name: %s"), *DisplayName);
}
else
{
NamesMap.Add(dec->GetActorLabel().ToString(), dec->GetActorPackage().ToString());
}
}
}
}
}
}
}
获得actor的display name 和 asset name