You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Optimized FFolder resolving to UActorFolder : now uses and maintains an acceleration table - Favor creation of FFolder using ActorFolderGuid when available - FFolder creation now always passes a Root Object to facilitate Root Object ptr resolving (even when it's the main world) - Fixed Duplicate Hierarchy when using Actor Folders and target level is different - Fixed copy/paste actor from Persistent to LevelInstance not loosing actor folder - Fixed mark for delete of an Actor Folder that could generate duplicates - Modified fix of duplicate Actor Folders in a level : instead of renaming duplicates, mark for delete all duplicates except one and redirect children to the one we keep #jira UE-150566 #rb patrick.enfedaque, jeanfrancois.dube #preflight 6284101f486700b561a555ff [CL 20346124 by Richard Malo in ue5-main branch]
2292 lines
75 KiB
C++
2292 lines
75 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "ContentStreaming.h"
|
|
#include "Misc/App.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/ReferenceChainSearch.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "UObject/LinkerLoad.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/Level.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Engine/World.h"
|
|
#include "UObject/ObjectRedirector.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/LevelStreamingAlwaysLoaded.h"
|
|
#include "Engine/LevelStreamingPersistent.h"
|
|
#include "Engine/LevelStreamingVolume.h"
|
|
#include "LevelUtils.h"
|
|
#include "EngineUtils.h"
|
|
#if WITH_EDITOR
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#endif
|
|
#include "Engine/LevelStreamingDynamic.h"
|
|
#include "Components/BrushComponent.h"
|
|
#include "Engine/CoreSettings.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "SceneInterface.h"
|
|
#include "Engine/NetDriver.h"
|
|
#include "Engine/NetConnection.h"
|
|
#include "Engine/PackageMapClient.h"
|
|
#include "Serialization/LoadTimeTrace.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogLevelStreaming);
|
|
|
|
#define LOCTEXT_NAMESPACE "World"
|
|
|
|
// CVars
|
|
namespace LevelStreamingCVars
|
|
{
|
|
// There are cases where we might have multiple visibility requests (and data) in flight leading to the server
|
|
// starting to replicate data based on an older visibility/streamingstatus update which can lead to broken channels
|
|
// to mitigate this problem we assign a TransactionId to each request/update to make sure that we are acting on the correct data
|
|
static bool bShouldClientUseMakingInvisibleTransactionRequest = false;
|
|
FAutoConsoleVariableRef CVarShouldClientUseMakingInvisibleTransactionRequest(
|
|
TEXT("LevelStreaming.ShouldClientUseMakingInvisibleTransactionRequest"),
|
|
bShouldClientUseMakingInvisibleTransactionRequest,
|
|
TEXT("Whether client should wait for server to acknowledge visibility update before making streaming levels invisible.\n")
|
|
TEXT("0: Disable, 1: Enable"),
|
|
ECVF_Default);
|
|
|
|
static bool bShouldServerUseMakingVisibleTransactionRequest = false;
|
|
FAutoConsoleVariableRef CVarShouldServerUseMakingVisibleTransactionRequest(
|
|
TEXT("LevelStreaming.ShouldServerUseMakingVisibleTransactionRequest"),
|
|
bShouldServerUseMakingVisibleTransactionRequest,
|
|
TEXT("Whether server should wait for client to acknowledge visibility update before treating streaming levels as visible by the client.\n")
|
|
TEXT("0: Disable, 1: Enable"),
|
|
ECVF_Default);
|
|
}
|
|
|
|
bool ULevelStreaming::ShouldClientUseMakingInvisibleTransactionRequest()
|
|
{
|
|
return LevelStreamingCVars::bShouldClientUseMakingInvisibleTransactionRequest;
|
|
}
|
|
|
|
bool ULevelStreaming::ShouldServerUseMakingVisibleTransactionRequest()
|
|
{
|
|
return LevelStreamingCVars::bShouldServerUseMakingVisibleTransactionRequest;
|
|
}
|
|
|
|
int32 ULevelStreamingDynamic::UniqueLevelInstanceId = 0;
|
|
|
|
/**
|
|
* This helper function is defined here so that it can go into the 4.18.1 hotfix (for UE-51791),
|
|
* even though it would make more logical sense to have this logic in a member function of UNetDriver.
|
|
* We're getting away with this because UNetDriver::GuidCache is (unfortunately) public.
|
|
*
|
|
* Renames any package entries in the GuidCache with a path matching UnPrefixedName to have a PIE prefix.
|
|
* This is needed because a client may receive an export for a level package before it's loaded and
|
|
* its name registered with FSoftObjectPath::AddPIEPackageName. In this case, the entry in the GuidCache
|
|
* will not be PIE-prefixed, but when the level is actually loaded, its package will be renamed with the
|
|
* prefix. Any subsequent references to this package won't resolve unless the name is fixed up.
|
|
*
|
|
* @param World the world whose NetDriver will be used for the rename
|
|
* @param UnPrefixedPackageName the path of the package to rename
|
|
*/
|
|
static void NetDriverRenameStreamingLevelPackageForPIE(const UWorld* World, FName UnPrefixedPackageName)
|
|
{
|
|
FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(World);
|
|
if (!WorldContext || WorldContext->WorldType != EWorldType::PIE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (FNamedNetDriver& Driver : WorldContext->ActiveNetDrivers)
|
|
{
|
|
if (Driver.NetDriver && Driver.NetDriver->GuidCache.IsValid())
|
|
{
|
|
for (TPair<FNetworkGUID, FNetGuidCacheObject>& GuidPair : Driver.NetDriver->GuidCache->ObjectLookup)
|
|
{
|
|
// Only look for packages, which will have a static GUID and an invalid OuterGUID.
|
|
const bool bIsPackage = GuidPair.Key.IsStatic() && !GuidPair.Value.OuterGUID.IsValid();
|
|
if (bIsPackage && GuidPair.Value.PathName == UnPrefixedPackageName)
|
|
{
|
|
GuidPair.Value.PathName = *UWorld::ConvertToPIEPackageName(GuidPair.Value.PathName.ToString(), WorldContext->PIEInstance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FStreamLevelAction::FStreamLevelAction(bool bIsLoading, const FName& InLevelName, bool bIsMakeVisibleAfterLoad, bool bInShouldBlock, const FLatentActionInfo& InLatentInfo, UWorld* World)
|
|
: bLoading(bIsLoading)
|
|
, bMakeVisibleAfterLoad(bIsMakeVisibleAfterLoad)
|
|
, bShouldBlock(bInShouldBlock)
|
|
, LevelName(InLevelName)
|
|
, LatentInfo(InLatentInfo)
|
|
{
|
|
ULevelStreaming* LocalLevel = FindAndCacheLevelStreamingObject( LevelName, World );
|
|
Level = LocalLevel;
|
|
ActivateLevel( LocalLevel );
|
|
}
|
|
|
|
void FStreamLevelAction::UpdateOperation(FLatentResponse& Response)
|
|
{
|
|
ULevelStreaming* LevelStreamingObject = Level.Get(); // to avoid confusion.
|
|
const bool bIsLevelValid = LevelStreamingObject != nullptr;
|
|
UE_LOG(LogLevelStreaming, Verbose, TEXT("FStreamLevelAction::UpdateOperation() LevelName %s, bIsLevelValid %d"), *LevelName.ToString(), (int32)bIsLevelValid);
|
|
if (bIsLevelValid)
|
|
{
|
|
bool bIsOperationFinished = UpdateLevel(LevelStreamingObject);
|
|
Response.FinishAndTriggerIf(bIsOperationFinished, LatentInfo.ExecutionFunction, LatentInfo.Linkage, LatentInfo.CallbackTarget);
|
|
}
|
|
else
|
|
{
|
|
Response.FinishAndTriggerIf(true, LatentInfo.ExecutionFunction, LatentInfo.Linkage, LatentInfo.CallbackTarget);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FString FStreamLevelAction::GetDescription() const
|
|
{
|
|
return FString::Printf(TEXT("Streaming Level in progress...(%s)"), *LevelName.ToString());
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Helper function to potentially find a level streaming object by name
|
|
*
|
|
* @param LevelName Name of level to search streaming object for in case Level is NULL
|
|
* @return level streaming object or NULL if none was found
|
|
*/
|
|
ULevelStreaming* FStreamLevelAction::FindAndCacheLevelStreamingObject( const FName LevelName, UWorld* InWorld )
|
|
{
|
|
// Search for the level object by name.
|
|
if( LevelName != NAME_None )
|
|
{
|
|
FString SearchPackageName = MakeSafeLevelName( LevelName, InWorld );
|
|
if (FPackageName::IsShortPackageName(SearchPackageName))
|
|
{
|
|
// Make sure MyMap1 and Map1 names do not resolve to a same streaming level
|
|
SearchPackageName = TEXT("/") + SearchPackageName;
|
|
}
|
|
|
|
for (ULevelStreaming* LevelStreaming : InWorld->GetStreamingLevels())
|
|
{
|
|
// We check only suffix of package name, to handle situations when packages were saved for play into a temporary folder
|
|
// Like Saved/Autosaves/PackageName
|
|
if (LevelStreaming &&
|
|
LevelStreaming->GetWorldAssetPackageName().EndsWith(SearchPackageName, ESearchCase::IgnoreCase))
|
|
{
|
|
return LevelStreaming;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Given a level name, returns a level name that will work with Play on Editor or Play on Console
|
|
*
|
|
* @param InLevelName Raw level name (no UEDPIE or UED<console> prefix)
|
|
* @param InWorld World in which to check for other instances of the name
|
|
*/
|
|
FString FStreamLevelAction::MakeSafeLevelName( const FName& InLevelName, UWorld* InWorld )
|
|
{
|
|
// Special case for PIE, the PackageName gets mangled.
|
|
if (!InWorld->StreamingLevelsPrefix.IsEmpty())
|
|
{
|
|
FString PackageName = FPackageName::GetShortName(InLevelName);
|
|
if (!PackageName.StartsWith(InWorld->StreamingLevelsPrefix))
|
|
{
|
|
PackageName = InWorld->StreamingLevelsPrefix + PackageName;
|
|
}
|
|
|
|
if (!FPackageName::IsShortPackageName(InLevelName))
|
|
{
|
|
PackageName = FPackageName::GetLongPackagePath(InLevelName.ToString()) + TEXT("/") + PackageName;
|
|
}
|
|
|
|
return PackageName;
|
|
}
|
|
|
|
return InLevelName.ToString();
|
|
}
|
|
/**
|
|
* Handles "Activated" for single ULevelStreaming object.
|
|
*
|
|
* @param LevelStreamingObject LevelStreaming object to handle "Activated" for.
|
|
*/
|
|
void FStreamLevelAction::ActivateLevel( ULevelStreaming* LevelStreamingObject )
|
|
{
|
|
if (LevelStreamingObject)
|
|
{
|
|
// Loading.
|
|
if (bLoading)
|
|
{
|
|
UE_LOG(LogStreaming, Log, TEXT("Streaming in level %s (%s)..."),*LevelStreamingObject->GetName(),*LevelStreamingObject->GetWorldAssetPackageName());
|
|
LevelStreamingObject->SetShouldBeLoaded(true);
|
|
LevelStreamingObject->SetShouldBeVisible(LevelStreamingObject->GetShouldBeVisibleFlag() || bMakeVisibleAfterLoad);
|
|
LevelStreamingObject->bShouldBlockOnLoad = bShouldBlock;
|
|
}
|
|
// Unloading.
|
|
else
|
|
{
|
|
UE_LOG(LogStreaming, Log, TEXT("Streaming out level %s (%s)..."),*LevelStreamingObject->GetName(),*LevelStreamingObject->GetWorldAssetPackageName());
|
|
LevelStreamingObject->SetShouldBeLoaded(false);
|
|
LevelStreamingObject->SetShouldBeVisible(false);
|
|
LevelStreamingObject->bShouldBlockOnUnload = bShouldBlock;
|
|
}
|
|
|
|
// If we have a valid world
|
|
if (UWorld* LevelWorld = LevelStreamingObject->GetWorld())
|
|
{
|
|
const bool bShouldBeLoaded = LevelStreamingObject->ShouldBeLoaded();
|
|
const bool bShouldBeVisible = LevelStreamingObject->ShouldBeVisible();
|
|
|
|
UE_LOG(LogLevel, Log, TEXT("ActivateLevel %s %i %i %i"),
|
|
*LevelStreamingObject->GetWorldAssetPackageName(),
|
|
bShouldBeLoaded,
|
|
bShouldBeVisible,
|
|
bShouldBlock);
|
|
|
|
// Notify players of the change
|
|
for (FConstPlayerControllerIterator Iterator = LevelWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
|
|
{
|
|
if (APlayerController* PlayerController = Iterator->Get())
|
|
{
|
|
PlayerController->LevelStreamingStatusChanged(
|
|
LevelStreamingObject,
|
|
bShouldBeLoaded,
|
|
bShouldBeVisible,
|
|
bShouldBlock,
|
|
INDEX_NONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevel, Warning, TEXT("Failed to find streaming level object associated with '%s'"), *LevelName.ToString() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles "UpdateOp" for single ULevelStreaming object.
|
|
*
|
|
* @param LevelStreamingObject LevelStreaming object to handle "UpdateOp" for.
|
|
*
|
|
* @return true if operation has completed, false if still in progress
|
|
*/
|
|
bool FStreamLevelAction::UpdateLevel( ULevelStreaming* LevelStreamingObject )
|
|
{
|
|
// No level streaming object associated with this sequence.
|
|
if (LevelStreamingObject == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
// Level is neither loaded nor should it be so we finished (in the sense that we have a pending GC request) unloading.
|
|
else if ((LevelStreamingObject->GetLoadedLevel() == nullptr) && !LevelStreamingObject->ShouldBeLoaded() )
|
|
{
|
|
return true;
|
|
}
|
|
// Level shouldn't be loaded but is as background level streaming is enabled so we need to fire finished event regardless.
|
|
else if (LevelStreamingObject->GetLoadedLevel() && !LevelStreamingObject->ShouldBeLoaded() && !GUseBackgroundLevelStreaming)
|
|
{
|
|
return true;
|
|
}
|
|
// Level is both loaded and wanted so we finished loading.
|
|
else if (LevelStreamingObject->GetLoadedLevel() && LevelStreamingObject->ShouldBeLoaded()
|
|
// Make sure we are visible if we are required to be so.
|
|
&& (!bMakeVisibleAfterLoad || LevelStreamingObject->GetLoadedLevel()->bIsVisible) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Loading/ unloading in progress.
|
|
return false;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
ULevelStreaming* implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
ULevelStreaming::ULevelStreaming(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, bIsStatic(false)
|
|
{
|
|
# if WITH_EDITORONLY_DATA
|
|
bShouldBeVisibleInEditor = true;
|
|
#endif
|
|
LevelColor = FLinearColor::White;
|
|
LevelTransform = FTransform::Identity;
|
|
MinTimeBetweenVolumeUnloadRequests = 2.0f;
|
|
bDrawOnLevelStatusMap = true;
|
|
LevelLODIndex = INDEX_NONE;
|
|
CurrentState = ECurrentState::Removed;
|
|
}
|
|
|
|
void ULevelStreaming::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
const bool PIESession = GetWorld()->WorldType == EWorldType::PIE || GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor);
|
|
|
|
#if WITH_EDITOR
|
|
// If this streaming level was saved with a short package name, try to convert it to a long package name
|
|
if ( !PIESession && PackageName_DEPRECATED != NAME_None )
|
|
{
|
|
const FString DeprecatedPackageNameString = PackageName_DEPRECATED.ToString();
|
|
if ( FPackageName::IsShortPackageName(PackageName_DEPRECATED) == false )
|
|
{
|
|
// Convert the FName reference to a TSoftObjectPtr, then broadcast that we loaded a reference
|
|
// so this reference is gathered by the cooker without having to resave the package.
|
|
SetWorldAssetByPackageName(PackageName_DEPRECATED);
|
|
WorldAsset.GetUniqueID().PostLoadPath(GetLinker());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevelStreaming, Display, TEXT("Invalid streaming level package name (%s). Only long package names are supported. This streaming level may not load or save properly."), *DeprecatedPackageNameString);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!IsValidStreamingLevel())
|
|
{
|
|
const FString WorldPackageName = GetWorldAssetPackageName();
|
|
UE_LOG(LogLevelStreaming, Display, TEXT("Failed to find streaming level package file: %s. This streaming level may not load or save properly."), *WorldPackageName);
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
// Launch notification to inform user of default change
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("PackageName"), FText::FromString(WorldPackageName));
|
|
FNotificationInfo Info(FText::Format(LOCTEXT("LevelStreamingFailToStreamLevel", "Failed to find streamed level {PackageName}, please fix the reference to it in the Level Browser"), Args));
|
|
Info.ExpireDuration = 7.0f;
|
|
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GetLinkerUEVersion() < VER_UE4_LEVEL_STREAMING_DRAW_COLOR_TYPE_CHANGE)
|
|
{
|
|
LevelColor = DrawColor_DEPRECATED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
UWorld* ULevelStreaming::GetWorld() const
|
|
{
|
|
// Fail gracefully if a CDO
|
|
if(IsTemplate())
|
|
{
|
|
return nullptr;
|
|
}
|
|
// Otherwise
|
|
else
|
|
{
|
|
return CastChecked<UWorld>(GetOuter());
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::Serialize( FArchive& Ar )
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
if (GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor) && GetOutermost()->GetPIEInstanceID() != INDEX_NONE)
|
|
{
|
|
RenameForPIE(GetOutermost()->GetPIEInstanceID());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::OnLevelAdded()
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
if (LoadedLevel->bIsVisible)
|
|
{
|
|
CurrentState = ECurrentState::LoadedVisible;
|
|
}
|
|
else
|
|
{
|
|
CurrentState = ECurrentState::LoadedNotVisible;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentState = ECurrentState::Unloaded;
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::OnLevelRemoved()
|
|
{
|
|
// If in one of the transitional states removing the level will be highly problematic
|
|
ensure(CurrentState != ECurrentState::Loading);
|
|
ensure(CurrentState != ECurrentState::MakingInvisible);
|
|
ensure(CurrentState != ECurrentState::MakingVisible);
|
|
|
|
CurrentState = ECurrentState::Removed;
|
|
}
|
|
|
|
bool ULevelStreaming::DetermineTargetState()
|
|
{
|
|
FScopeCycleCounterUObject ContextScope(this);
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
#if WITH_EDITOR
|
|
// Don't bother loading sub-levels in PIE for levels that aren't visible in editor
|
|
if (World->IsPlayInEditor() && GEngine->OnlyLoadEditorVisibleLevelsInPIE())
|
|
{
|
|
if (!GetShouldBeVisibleInEditor())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool bContinueToConsider = true;
|
|
|
|
switch(CurrentState)
|
|
{
|
|
case ECurrentState::MakingVisible:
|
|
ensure(LoadedLevel);
|
|
if (!ShouldBeVisible() && GetWorld()->GetCurrentLevelPendingVisibility() != LoadedLevel)
|
|
{
|
|
// Since level doesn't need to be visible anymore, change TargetState to ETargetState::LoadedNotVisible.
|
|
// Next UpdateStreamingState will handle switching CurrentState to ECurrentState::LoadedNotVisible.
|
|
// From there, regular flow will properly handle TargetState.
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else
|
|
{
|
|
TargetState = ETargetState::LoadedVisible;
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::MakingInvisible:
|
|
ensure(LoadedLevel);
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
break;
|
|
|
|
case ECurrentState::Loading:
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
break;
|
|
|
|
case ECurrentState::Unloaded:
|
|
if (bIsRequestingUnloadAndRemoval)
|
|
{
|
|
TargetState = ETargetState::UnloadedAndRemoved;
|
|
}
|
|
else if (World->GetShouldForceUnloadStreamingLevels())
|
|
{
|
|
bContinueToConsider = false;
|
|
}
|
|
else if (!World->IsGameWorld())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else if (ShouldBeLoaded())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else
|
|
{
|
|
bContinueToConsider = false;
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::LoadedNotVisible:
|
|
if (bIsRequestingUnloadAndRemoval || World->GetShouldForceUnloadStreamingLevels())
|
|
{
|
|
TargetState = ETargetState::Unloaded;
|
|
}
|
|
else if (World->IsGameWorld() && !ShouldBeLoaded())
|
|
{
|
|
TargetState = ETargetState::Unloaded;
|
|
}
|
|
else if (!IsDesiredLevelLoaded())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else if (ShouldBeVisible())
|
|
{
|
|
TargetState = ETargetState::LoadedVisible;
|
|
}
|
|
else
|
|
{
|
|
bContinueToConsider = false;
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::LoadedVisible:
|
|
if (bIsRequestingUnloadAndRemoval || World->GetShouldForceUnloadStreamingLevels())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else if (World->IsGameWorld() && !ShouldBeLoaded())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else if (!ShouldBeVisible())
|
|
{
|
|
TargetState = ETargetState::LoadedNotVisible;
|
|
}
|
|
else if (!IsDesiredLevelLoaded())
|
|
{
|
|
TargetState = ETargetState::LoadedVisible;
|
|
}
|
|
else
|
|
{
|
|
bContinueToConsider = false;
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::FailedToLoad:
|
|
|
|
// Anything that affects whether we might try to reload changes current state itself
|
|
bContinueToConsider = false;
|
|
break;
|
|
|
|
case ECurrentState::Removed:
|
|
|
|
// Never continue to consider a removed streaming level
|
|
bContinueToConsider = false;
|
|
break;
|
|
|
|
default:
|
|
ensure(false);
|
|
}
|
|
|
|
return bContinueToConsider;
|
|
}
|
|
|
|
static bool ShouldWaitForServerAckBeforeMakingInvisible(const UWorld* World, const ULevel* LoadedLevel, const bool bShouldBeVisible, FNetLevelVisibilityState& NetVisibilityState)
|
|
{
|
|
if (ULevelStreaming::ShouldClientUseMakingInvisibleTransactionRequest())
|
|
{
|
|
if (!LoadedLevel->bClientOnlyVisible && World->IsNetMode(NM_Client) && (World->NetDriver && World->NetDriver->ServerConnection->GetConnectionState() == USOCK_Open))
|
|
{
|
|
if (NetVisibilityState.PendingClientRequestIndex != NetVisibilityState.AckedClientRequestIndex)
|
|
{
|
|
// Wait for server to acknowledge the visibility change so that we are sure that we have no incoming data in flight when removing the actors from the world
|
|
return true;
|
|
}
|
|
else if (!bShouldBeVisible && NetVisibilityState.bPendingInvisibleRequest && NetVisibilityState.PendingClientRequestIndex == NetVisibilityState.AckedClientRequestIndex)
|
|
{
|
|
// We have a pending request, IncrementTransactionIndex and send ServerUpdateLevelVisibility request to server
|
|
FNetLevelVisibilityTransactionId TransactionId;
|
|
TransactionId.SetIsClientInstigator(true);
|
|
TransactionId.SetTransactionIndex(NetVisibilityState.PendingClientRequestIndex);
|
|
NetVisibilityState.PendingClientRequestIndex = TransactionId.IncrementTransactionIndex();
|
|
NetVisibilityState.bPendingInvisibleRequest = false;
|
|
|
|
for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
|
|
{
|
|
if (APlayerController* PlayerController = Iterator->Get())
|
|
{
|
|
FUpdateLevelVisibilityLevelInfo LevelVisibility(LoadedLevel, false);
|
|
LevelVisibility.PackageName = PlayerController->NetworkRemapPath(LevelVisibility.PackageName, false);
|
|
LevelVisibility.VisibilityRequestId = TransactionId;
|
|
|
|
PlayerController->ServerUpdateLevelVisibility(LevelVisibility);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invalidate request
|
|
NetVisibilityState.bPendingInvisibleRequest = false;
|
|
NetVisibilityState.AckedClientRequestIndex = NetVisibilityState.PendingClientRequestIndex;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ULevelStreaming::IsWaitingForNetVisibilityTransactionAck() const
|
|
{
|
|
if (ShouldClientUseMakingInvisibleTransactionRequest())
|
|
{
|
|
if (LoadedLevel && !LoadedLevel->bClientOnlyVisible)
|
|
{
|
|
const UWorld* World = GetWorld();
|
|
if (World->IsNetMode(NM_Client) && (World->NetDriver && World->NetDriver->ServerConnection->GetConnectionState() == USOCK_Open) && ((NetVisibilityState.PendingClientRequestIndex != NetVisibilityState.AckedClientRequestIndex) || NetVisibilityState.bPendingInvisibleRequest))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULevelStreaming::UpdateNetVisibilityTransactionState(bool bInShouldBeVisible, FNetLevelVisibilityTransactionId TransactionId)
|
|
{
|
|
if (GetCurrentState() != ULevelStreaming::ECurrentState::MakingInvisible)
|
|
{
|
|
// If this is a client request to unload, we will conditionally send a notification to the server before we make the level invisible
|
|
const bool bIsClientTransaction = TransactionId.IsClientTransaction();
|
|
|
|
NetVisibilityState.bPendingInvisibleRequest = bIsClientTransaction && (bInShouldBeVisible == false);
|
|
NetVisibilityState.ServerRequestIndex = bIsClientTransaction ? FNetLevelVisibilityTransactionId::InvalidTransactionIndex : TransactionId.GetTransactionIndex();
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::BeginClientNetVisibilityRequest(bool bInShouldBeVisible)
|
|
{
|
|
FNetLevelVisibilityTransactionId TransactionId;
|
|
TransactionId.SetIsClientInstigator(true);
|
|
|
|
UpdateNetVisibilityTransactionState(bInShouldBeVisible, TransactionId);
|
|
}
|
|
|
|
void ULevelStreaming::AckNetVisibilityTransaction(FNetLevelVisibilityTransactionId AckedClientTransactionId)
|
|
{
|
|
if (ensure(NetVisibilityState.PendingClientRequestIndex != NetVisibilityState.AckedClientRequestIndex))
|
|
{
|
|
// Invalidate the pending request as there should only ever be a single one in flight
|
|
NetVisibilityState.AckedClientRequestIndex = AckedClientTransactionId.GetTransactionIndex();
|
|
NetVisibilityState.bPendingInvisibleRequest = false;
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::UpdateStreamingState(bool& bOutUpdateAgain, bool& bOutRedetermineTarget)
|
|
{
|
|
FScopeCycleCounterUObject ContextScope(this);
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
bOutUpdateAgain = false;
|
|
bOutRedetermineTarget = false;
|
|
|
|
auto UpdateStreamingState_RequestLevel = [&]()
|
|
{
|
|
bool bBlockOnLoad = (bShouldBlockOnLoad || ShouldBeAlwaysLoaded());
|
|
const bool bAllowLevelLoadRequests = (bBlockOnLoad || World->AllowLevelLoadRequests());
|
|
bBlockOnLoad |= (!GUseBackgroundLevelStreaming || !World->IsGameWorld());
|
|
|
|
const ECurrentState PreviousState = CurrentState;
|
|
|
|
RequestLevel(World, bAllowLevelLoadRequests, (bBlockOnLoad ? ULevelStreaming::AlwaysBlock : ULevelStreaming::BlockAlwaysLoadedLevelsOnly));
|
|
|
|
if (CurrentState != ECurrentState::Loading)
|
|
{
|
|
bOutRedetermineTarget = true;
|
|
|
|
if (CurrentState != PreviousState)
|
|
{
|
|
bOutUpdateAgain = true;
|
|
}
|
|
}
|
|
|
|
if (LoadedLevel == nullptr)
|
|
{
|
|
DiscardPendingUnloadLevel(World);
|
|
}
|
|
};
|
|
|
|
switch(CurrentState)
|
|
{
|
|
case ECurrentState::MakingVisible:
|
|
if (ensure(LoadedLevel))
|
|
{
|
|
// Handle case where MakingVisible is not needed anymore
|
|
if (TargetState == ETargetState::LoadedNotVisible)
|
|
{
|
|
CurrentState = ECurrentState::LoadedNotVisible;
|
|
bOutUpdateAgain = true;
|
|
bOutRedetermineTarget = true;
|
|
}
|
|
else
|
|
{
|
|
// Only respond with ServerTransactionId if the is the target visibility state is supposed to be visible
|
|
FNetLevelVisibilityTransactionId TransactionId;
|
|
if (bShouldBeVisible)
|
|
{
|
|
TransactionId.SetTransactionIndex(NetVisibilityState.ServerRequestIndex);
|
|
}
|
|
World->AddToWorld(LoadedLevel, LevelTransform, !bShouldBlockOnLoad, TransactionId);
|
|
|
|
if (LoadedLevel->bIsVisible)
|
|
{
|
|
// immediately discard previous level
|
|
DiscardPendingUnloadLevel(World);
|
|
|
|
if (World->Scene)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateLevelStreamingInner_OnLevelAddedToWorld);
|
|
// Notify the new level has been added after the old has been discarded
|
|
World->Scene->OnLevelAddedToWorld(LoadedLevel->GetOutermost()->GetFName(), World, LoadedLevel->bIsLightingScenario);
|
|
}
|
|
|
|
CurrentState = ECurrentState::LoadedVisible;
|
|
bOutUpdateAgain = true;
|
|
bOutRedetermineTarget = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::MakingInvisible:
|
|
if (ensure(LoadedLevel))
|
|
{
|
|
auto RemoveLevelFromScene = [World, this]()
|
|
{
|
|
if (World->Scene)
|
|
{
|
|
World->Scene->OnLevelRemovedFromWorld(LoadedLevel->GetOutermost()->GetFName(), World, LoadedLevel->bIsLightingScenario);
|
|
}
|
|
};
|
|
|
|
const bool bWasVisible = LoadedLevel->bIsVisible;
|
|
|
|
// We do not want to have any changes in flights when ending play, so before making invisible we wait for server acknowledgment
|
|
if (ShouldWaitForServerAckBeforeMakingInvisible(World, LoadedLevel, bShouldBeVisible, NetVisibilityState))
|
|
{
|
|
break;
|
|
}
|
|
|
|
FNetLevelVisibilityTransactionId TransactionId;
|
|
// Only respond with ServerTransactionId if the is the target visibility state is supposed to be not visible
|
|
if (bShouldBeVisible == false)
|
|
{
|
|
TransactionId.SetTransactionIndex(NetVisibilityState.ServerRequestIndex);
|
|
}
|
|
|
|
// Hide loaded level, incrementally if necessary
|
|
World->RemoveFromWorld(LoadedLevel, !bShouldBlockOnUnload && World->IsGameWorld(), TransactionId);
|
|
|
|
// Hide loaded level immediately if bRequireFullVisibilityToRender is set
|
|
const bool LevelBecameInvisible = bWasVisible && !LoadedLevel->bIsVisible;
|
|
if (LoadedLevel->bRequireFullVisibilityToRender && LevelBecameInvisible)
|
|
{
|
|
RemoveLevelFromScene();
|
|
}
|
|
|
|
// If the level is now hidden & all components have been removed from the world
|
|
const bool LevelWasRemovedFromWorld = !LoadedLevel->bIsVisible && !LoadedLevel->bIsBeingRemoved;
|
|
if (LevelWasRemovedFromWorld)
|
|
{
|
|
// Remove level from scene if we haven't done it already
|
|
if (!LoadedLevel->bRequireFullVisibilityToRender)
|
|
{
|
|
RemoveLevelFromScene();
|
|
}
|
|
|
|
CurrentState = ECurrentState::LoadedNotVisible;
|
|
bOutUpdateAgain = true;
|
|
bOutRedetermineTarget = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::Loading:
|
|
// Just waiting
|
|
break;
|
|
|
|
case ECurrentState::Unloaded:
|
|
|
|
switch (TargetState)
|
|
{
|
|
case ETargetState::LoadedNotVisible:
|
|
{
|
|
UpdateStreamingState_RequestLevel();
|
|
}
|
|
break;
|
|
|
|
case ETargetState::UnloadedAndRemoved:
|
|
World->RemoveStreamingLevel(this);
|
|
bOutRedetermineTarget = true;
|
|
break;
|
|
|
|
default:
|
|
ensure(false);
|
|
}
|
|
break;
|
|
|
|
case ECurrentState::LoadedNotVisible:
|
|
switch (TargetState)
|
|
{
|
|
case ETargetState::LoadedVisible:
|
|
CurrentState = ECurrentState::MakingVisible;
|
|
bOutUpdateAgain = true;
|
|
break;
|
|
|
|
case ETargetState::Unloaded:
|
|
DiscardPendingUnloadLevel(World);
|
|
ClearLoadedLevel();
|
|
DiscardPendingUnloadLevel(World);
|
|
|
|
bOutUpdateAgain = true;
|
|
bOutRedetermineTarget = true;
|
|
break;
|
|
|
|
case ETargetState::LoadedNotVisible:
|
|
if (LoadedLevel && !IsDesiredLevelLoaded())
|
|
{
|
|
// Process PendingUnloadLevel to unblock level streaming state machine (no new request will start while there's a pending level to unload)
|
|
// This rare case can happen if desired level (typically LODPackage) changed between last RequestLevel call and AsyncLevelLoadComplete completion callback.
|
|
DiscardPendingUnloadLevel(World);
|
|
}
|
|
UpdateStreamingState_RequestLevel();
|
|
break;
|
|
|
|
default:
|
|
ensure(false);
|
|
}
|
|
|
|
break;
|
|
|
|
case ECurrentState::LoadedVisible:
|
|
switch (TargetState)
|
|
{
|
|
case ETargetState::LoadedNotVisible:
|
|
CurrentState = ECurrentState::MakingInvisible;
|
|
bOutUpdateAgain = true;
|
|
break;
|
|
|
|
case ETargetState::LoadedVisible:
|
|
UpdateStreamingState_RequestLevel();
|
|
break;
|
|
|
|
default:
|
|
ensure(false);
|
|
}
|
|
|
|
break;
|
|
|
|
case ECurrentState::FailedToLoad:
|
|
bOutRedetermineTarget = true;
|
|
break;
|
|
|
|
default:
|
|
ensureMsgf(false, TEXT("Unexpected state in ULevelStreaming::UpdateStreamingState for '%s'. CurrentState='%s' TargetState='%s'"), *GetPathName(), EnumToString(CurrentState), EnumToString(TargetState));
|
|
}
|
|
}
|
|
|
|
const TCHAR* ULevelStreaming::EnumToString(ECurrentState InCurrentState)
|
|
{
|
|
switch (InCurrentState)
|
|
{
|
|
case ECurrentState::Removed:
|
|
return TEXT("Removed");
|
|
case ECurrentState::Unloaded:
|
|
return TEXT("Unloaded");
|
|
case ECurrentState::FailedToLoad:
|
|
return TEXT("FailedToLoad");
|
|
case ECurrentState::Loading:
|
|
return TEXT("Loading");
|
|
case ECurrentState::LoadedNotVisible:
|
|
return TEXT("LoadedNotVisible");
|
|
case ECurrentState::MakingVisible:
|
|
return TEXT("MakingVisible");
|
|
case ECurrentState::LoadedVisible:
|
|
return TEXT("LoadedVisible");
|
|
case ECurrentState::MakingInvisible:
|
|
return TEXT("MakingInvisible");
|
|
}
|
|
ensure(false);
|
|
return TEXT("Unknown");
|
|
}
|
|
|
|
const TCHAR* ULevelStreaming::EnumToString(ETargetState InTargetState)
|
|
{
|
|
switch (InTargetState)
|
|
{
|
|
case ETargetState::Unloaded:
|
|
return TEXT("Unloaded");
|
|
case ETargetState::UnloadedAndRemoved:
|
|
return TEXT("UnloadedAndRemoved");
|
|
case ETargetState::LoadedNotVisible:
|
|
return TEXT("LoadedNotVisible");
|
|
case ETargetState::LoadedVisible:
|
|
return TEXT("LoadedVisible");
|
|
}
|
|
ensure(false);
|
|
return TEXT("Unknown");
|
|
}
|
|
|
|
FName ULevelStreaming::GetLODPackageName() const
|
|
{
|
|
if (LODPackageNames.IsValidIndex(LevelLODIndex))
|
|
{
|
|
return LODPackageNames[LevelLODIndex];
|
|
}
|
|
else
|
|
{
|
|
return GetWorldAssetPackageFName();
|
|
}
|
|
}
|
|
|
|
FName ULevelStreaming::GetLODPackageNameToLoad() const
|
|
{
|
|
if (LODPackageNames.IsValidIndex(LevelLODIndex))
|
|
{
|
|
return LODPackageNamesToLoad.IsValidIndex(LevelLODIndex) ? LODPackageNamesToLoad[LevelLODIndex] : NAME_None;
|
|
}
|
|
else
|
|
{
|
|
return PackageNameToLoad;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULevelStreaming::RemoveLevelFromCollectionForReload()
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
// Remove the loaded level from its current collection, if any.
|
|
if (LoadedLevel->GetCachedLevelCollection())
|
|
{
|
|
LoadedLevel->GetCachedLevelCollection()->RemoveLevel(LoadedLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::AddLevelToCollectionAfterReload()
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
// Remove the loaded level from its current collection, if any.
|
|
if (LoadedLevel->GetCachedLevelCollection())
|
|
{
|
|
LoadedLevel->GetCachedLevelCollection()->RemoveLevel(LoadedLevel);
|
|
}
|
|
// Add this level to the correct collection
|
|
const ELevelCollectionType CollectionType = bIsStatic ? ELevelCollectionType::StaticLevels : ELevelCollectionType::DynamicSourceLevels;
|
|
FLevelCollection& LC = GetWorld()->FindOrAddCollectionByType(CollectionType);
|
|
LC.AddLevel(LoadedLevel);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FUObjectAnnotationSparse<ULevelStreaming::FLevelAnnotation, false> ULevelStreaming::LevelAnnotations;
|
|
|
|
void ULevelStreaming::RemoveLevelAnnotation(const ULevel* Level)
|
|
{
|
|
ULevelStreaming::LevelAnnotations.RemoveAnnotation(Level);
|
|
}
|
|
|
|
ULevelStreaming* ULevelStreaming::FindStreamingLevel(const ULevel* Level)
|
|
{
|
|
ULevelStreaming* FoundLevelStreaming = nullptr;
|
|
if (Level && Level->OwningWorld && !Level->IsPersistentLevel())
|
|
{
|
|
FLevelAnnotation LevelAnnotation = LevelAnnotations.GetAnnotation(Level);
|
|
if (LevelAnnotation.LevelStreaming)
|
|
{
|
|
check(LevelAnnotation.LevelStreaming->GetLoadedLevel() == Level);
|
|
FoundLevelStreaming = LevelAnnotation.LevelStreaming;
|
|
}
|
|
|
|
if (!FoundLevelStreaming)
|
|
{
|
|
// fallback search
|
|
for (ULevelStreaming* CurStreamingLevel : Level->OwningWorld->GetStreamingLevels())
|
|
{
|
|
if (CurStreamingLevel && CurStreamingLevel->GetLoadedLevel() == Level)
|
|
{
|
|
FoundLevelStreaming = CurStreamingLevel;
|
|
break;
|
|
}
|
|
}
|
|
// we shouldn't have found a streaming level here
|
|
ensure(!FoundLevelStreaming);
|
|
}
|
|
}
|
|
|
|
return FoundLevelStreaming;
|
|
}
|
|
|
|
void ULevelStreaming::SetLoadedLevel(ULevel* Level)
|
|
{
|
|
// Pending level should be unloaded at this point
|
|
check(PendingUnloadLevel == nullptr);
|
|
PendingUnloadLevel = LoadedLevel;
|
|
LoadedLevel = Level;
|
|
bHasCachedLoadedLevelPackageName = false;
|
|
|
|
// Cancel unloading for this level, in case it was queued for it
|
|
FLevelStreamingGCHelper::CancelUnloadRequest(LoadedLevel);
|
|
|
|
// Add this level to the correct collection
|
|
const ELevelCollectionType CollectionType = bIsStatic ? ELevelCollectionType::StaticLevels : ELevelCollectionType::DynamicSourceLevels;
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
FLevelCollection& LC = World->FindOrAddCollectionByType(CollectionType);
|
|
LC.RemoveLevel(PendingUnloadLevel);
|
|
|
|
if (PendingUnloadLevel)
|
|
{
|
|
RemoveLevelAnnotation(PendingUnloadLevel);
|
|
}
|
|
|
|
if (LoadedLevel)
|
|
{
|
|
LoadedLevel->OwningWorld = World;
|
|
ULevelStreaming::LevelAnnotations.AddAnnotation(LoadedLevel, FLevelAnnotation(this));
|
|
|
|
// Remove the loaded level from its current collection, if any.
|
|
if (LoadedLevel->GetCachedLevelCollection())
|
|
{
|
|
LoadedLevel->GetCachedLevelCollection()->RemoveLevel(LoadedLevel);
|
|
}
|
|
LC.AddLevel(LoadedLevel);
|
|
|
|
CurrentState = (LoadedLevel->bIsVisible ? ECurrentState::LoadedVisible : ECurrentState::LoadedNotVisible);
|
|
}
|
|
else
|
|
{
|
|
CurrentState = ECurrentState::Unloaded;
|
|
}
|
|
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
|
|
// Virtual call for derived classes to add their logic
|
|
OnLevelLoadedChanged(LoadedLevel);
|
|
|
|
if (LoadedLevel)
|
|
{
|
|
LoadedLevel->OnLevelLoaded();
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::DiscardPendingUnloadLevel(UWorld* PersistentWorld)
|
|
{
|
|
if (PendingUnloadLevel)
|
|
{
|
|
if (PendingUnloadLevel->bIsVisible)
|
|
{
|
|
PersistentWorld->RemoveFromWorld(PendingUnloadLevel);
|
|
}
|
|
|
|
if (!PendingUnloadLevel->bIsVisible)
|
|
{
|
|
FLevelStreamingGCHelper::RequestUnload(PendingUnloadLevel);
|
|
PendingUnloadLevel = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ULevelStreaming::IsDesiredLevelLoaded() const
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
const bool bIsGameWorld = GetWorld()->IsGameWorld();
|
|
const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName();
|
|
const FName LoadedLevelPackageName = GetLoadedLevelPackageName();
|
|
|
|
return (LoadedLevelPackageName == DesiredPackageName);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULevelStreaming::PrepareLoadedLevel(ULevel* InLevel, UPackage* InLevelPackage, int32 InPIEInstanceID)
|
|
{
|
|
check(InLevel);
|
|
UWorld* LevelOwningWorld = InLevel->OwningWorld;
|
|
|
|
if (ensure(LevelOwningWorld))
|
|
{
|
|
ULevel* PendingLevelVisOrInvis = (LevelOwningWorld->GetCurrentLevelPendingVisibility() ? LevelOwningWorld->GetCurrentLevelPendingVisibility() : LevelOwningWorld->GetCurrentLevelPendingInvisibility());
|
|
if (PendingLevelVisOrInvis && PendingLevelVisOrInvis == LoadedLevel)
|
|
{
|
|
// We can't change current loaded level if it's still processing visibility request
|
|
// On next UpdateLevelStreaming call this loaded package will be found in memory by RequestLevel function in case visibility request has finished
|
|
UE_LOG(LogLevelStreaming, Verbose, TEXT("Delaying setting result of async load new level %s, because current loaded level still processing visibility request"), *InLevelPackage->GetName());
|
|
}
|
|
else
|
|
{
|
|
check(PendingUnloadLevel == nullptr);
|
|
|
|
#if WITH_EDITOR
|
|
if (InPIEInstanceID != INDEX_NONE)
|
|
{
|
|
InLevel->FixupForPIE(InPIEInstanceID);
|
|
}
|
|
#endif
|
|
SetLoadedLevel(InLevel);
|
|
// Broadcast level loaded event to blueprints
|
|
OnLevelLoaded.Broadcast();
|
|
}
|
|
}
|
|
|
|
InLevel->HandleLegacyMapBuildData();
|
|
|
|
// Notify the streamer to start building incrementally the level streaming data.
|
|
IStreamingManager::Get().AddLevel(InLevel);
|
|
|
|
// Make sure this level will start to render only when it will be fully added to the world
|
|
if (ShouldRequireFullVisibilityToRender())
|
|
{
|
|
InLevel->bRequireFullVisibilityToRender = true;
|
|
// LOD levels should not be visible on server
|
|
if (LODPackageNames.Num() > 0)
|
|
{
|
|
InLevel->bClientOnlyVisible = LODPackageNames.Contains(InLevelPackage->GetFName());
|
|
}
|
|
}
|
|
|
|
// Apply streaming level property to level
|
|
InLevel->bClientOnlyVisible |= bClientOnlyVisible;
|
|
|
|
// In the editor levels must be in the levels array regardless of whether they are visible or not
|
|
if (ensure(LevelOwningWorld) && LevelOwningWorld->WorldType == EWorldType::Editor)
|
|
{
|
|
LevelOwningWorld->AddLevel(InLevel);
|
|
#if WITH_EDITOR
|
|
// We should also at this point, apply the level's editor transform
|
|
if (!InLevel->bAlreadyMovedActors)
|
|
{
|
|
FLevelUtils::ApplyEditorTransform(this, false);
|
|
InLevel->bAlreadyMovedActors = true;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
}
|
|
|
|
bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoadRequests, EReqLevelBlock BlockPolicy)
|
|
{
|
|
// Quit early in case load request already issued
|
|
if (CurrentState == ECurrentState::Loading)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Previous attempts have failed, no reason to try again
|
|
if (CurrentState == ECurrentState::FailedToLoad)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ULevelStreaming_RequestLevel);
|
|
FScopeCycleCounterUObject Context(PersistentWorld);
|
|
|
|
// Package name we want to load
|
|
const bool bIsGameWorld = PersistentWorld->IsGameWorld();
|
|
const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName();
|
|
const FName LoadedLevelPackageName = GetLoadedLevelPackageName();
|
|
|
|
// Check if currently loaded level is what we want right now
|
|
if (LoadedLevel && LoadedLevelPackageName == DesiredPackageName)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Can not load new level now, there is still level pending unload
|
|
if (PendingUnloadLevel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Can not load new level now either, we're still processing visibility for this one
|
|
ULevel* PendingLevelVisOrInvis = (PersistentWorld->GetCurrentLevelPendingVisibility() ? PersistentWorld->GetCurrentLevelPendingVisibility() : PersistentWorld->GetCurrentLevelPendingInvisibility());
|
|
if (PendingLevelVisOrInvis && PendingLevelVisOrInvis == LoadedLevel)
|
|
{
|
|
UE_LOG(LogLevelStreaming, Verbose, TEXT("Delaying load of new level %s, because %s still processing visibility request."), *DesiredPackageName.ToString(), *CachedLoadedLevelPackageName.ToString());
|
|
return false;
|
|
}
|
|
|
|
// Validate that our new streaming level is unique, check for clash with currently loaded streaming levels
|
|
for (ULevelStreaming* OtherLevel : PersistentWorld->GetStreamingLevels())
|
|
{
|
|
if (OtherLevel == nullptr || OtherLevel == this)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const ECurrentState OtherState = OtherLevel->GetCurrentState();
|
|
if (OtherState == ECurrentState::FailedToLoad || OtherState == ECurrentState::Removed || (OtherState == ECurrentState::Unloaded && (OtherLevel->TargetState == ETargetState::Unloaded || OtherLevel->TargetState == ETargetState::UnloadedAndRemoved)))
|
|
{
|
|
// If the other level isn't loaded or in the process of being loaded we don't need to consider it
|
|
continue;
|
|
}
|
|
|
|
if (OtherLevel->WorldAsset == WorldAsset)
|
|
{
|
|
if (OtherLevel->GetIsRequestingUnloadAndRemoval())
|
|
{
|
|
return false; // Cannot load new level now, retry until the OtherLevel is done unloading
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevelStreaming, Warning, TEXT("Streaming Level '%s' uses same destination for level ('%s') as '%s'. Level cannot be loaded again and this StreamingLevel will be flagged as failed to load."), *GetPathName(), *WorldAsset.GetLongPackageName(), *OtherLevel->GetPathName());
|
|
CurrentState = ECurrentState::FailedToLoad;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_LOADTIME_REQUEST_GROUP_SCOPE(TEXT("LevelStreaming - %s"), *GetPathName());
|
|
|
|
EPackageFlags PackageFlags = PKG_ContainsMap;
|
|
int32 PIEInstanceID = INDEX_NONE;
|
|
|
|
// Try to find the [to be] loaded package.
|
|
UWorld* World = nullptr;
|
|
UPackage* LevelPackage = (UPackage*)StaticFindObjectFast(UPackage::StaticClass(), nullptr, DesiredPackageName, 0, 0, RF_NoFlags, EInternalObjectFlags::Garbage);
|
|
|
|
if (LevelPackage)
|
|
{
|
|
// Find world object and use its PersistentLevel pointer.
|
|
World = UWorld::FindWorldInPackage(LevelPackage);
|
|
|
|
// Check for a redirector. Follow it, if found.
|
|
if (!World)
|
|
{
|
|
World = UWorld::FollowWorldRedirectorInPackage(LevelPackage);
|
|
LevelPackage = World ? World->GetOutermost() : nullptr;
|
|
}
|
|
}
|
|
|
|
// copy streaming level on demand if we are in PIE
|
|
// (the world is already loaded for the editor, just find it and copy it)
|
|
if ( LevelPackage == nullptr && PersistentWorld->IsPlayInEditor() )
|
|
{
|
|
if (PersistentWorld->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor))
|
|
{
|
|
PackageFlags |= PKG_PlayInEditor;
|
|
}
|
|
PIEInstanceID = PersistentWorld->GetOutermost()->GetPIEInstanceID();
|
|
|
|
const FString NonPrefixedLevelName = UWorld::StripPIEPrefixFromPackageName(DesiredPackageName.ToString(), PersistentWorld->StreamingLevelsPrefix);
|
|
UPackage* EditorLevelPackage = FindObjectFast<UPackage>(nullptr, FName(*NonPrefixedLevelName));
|
|
|
|
bool bShouldDuplicate = EditorLevelPackage && (BlockPolicy == AlwaysBlock || EditorLevelPackage->IsDirty() || !GEngine->PreferToStreamLevelsInPIE());
|
|
if (bShouldDuplicate)
|
|
{
|
|
// Do the duplication
|
|
UWorld* PIELevelWorld = UWorld::DuplicateWorldForPIE(NonPrefixedLevelName, PersistentWorld);
|
|
if (PIELevelWorld)
|
|
{
|
|
check(PendingUnloadLevel == NULL);
|
|
SetLoadedLevel(PIELevelWorld->PersistentLevel);
|
|
|
|
// Broadcast level loaded event to blueprints
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_OnLevelLoaded_Broadcast);
|
|
OnLevelLoaded.Broadcast();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (PersistentWorld->WorldComposition == NULL) // In world composition streaming levels are not loaded by default
|
|
{
|
|
if ( bAllowLevelLoadRequests )
|
|
{
|
|
UE_LOG(LogLevelStreaming, Log, TEXT("World to duplicate for PIE '%s' not found. Attempting load."), *NonPrefixedLevelName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevelStreaming, Warning, TEXT("Unable to duplicate PIE World: '%s'"), *NonPrefixedLevelName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Package is already or still loaded.
|
|
if (LevelPackage)
|
|
{
|
|
if (World != nullptr)
|
|
{
|
|
if (!IsValid(World))
|
|
{
|
|
// We're trying to reload a level that has very recently been marked for garbage collection, it might not have been cleaned up yet
|
|
// So continue attempting to reload the package if possible
|
|
UE_LOG(LogLevelStreaming, Verbose, TEXT("RequestLevel: World is pending kill %s"), *DesiredPackageName.ToString());
|
|
return false;
|
|
}
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
if (World->PersistentLevel == nullptr)
|
|
{
|
|
UE_LOG(LogLevelStreaming, Error, TEXT("World exists but PersistentLevel doesn't for %s, most likely caused by reference to world of unloaded level and GC setting reference to null while keeping world object"), *World->GetOutermost()->GetName());
|
|
UE_LOG(LogLevelStreaming, Error, TEXT("Most likely caused by reference to world of unloaded level and GC setting reference to null while keeping world object. Referenced by:"));
|
|
|
|
UEngine::FindAndPrintStaleReferencesToObject(World, UObjectBaseUtility::IsPendingKillEnabled() ? EPrintStaleReferencesOptions::Fatal : (EPrintStaleReferencesOptions::Error | EPrintStaleReferencesOptions::Ensure));
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
if (World->PersistentLevel != LoadedLevel)
|
|
{
|
|
// Level already exists but may have the wrong type due to being inactive before, so copy data over
|
|
World->WorldType = PersistentWorld->WorldType;
|
|
World->PersistentLevel->OwningWorld = PersistentWorld;
|
|
|
|
PrepareLoadedLevel(World->PersistentLevel, LevelPackage, PIEInstanceID);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Async load package if world object couldn't be found and we are allowed to request a load.
|
|
if (bAllowLevelLoadRequests)
|
|
{
|
|
const FName DesiredPackageNameToLoad = bIsGameWorld ? GetLODPackageNameToLoad() : PackageNameToLoad;
|
|
FString NormalizedPackageName = (DesiredPackageNameToLoad.IsNone() ? DesiredPackageName : DesiredPackageNameToLoad).ToString();
|
|
// The PackageName might be an objectpath; convert it to a packagename if it is not one already
|
|
NormalizedPackageName = FPackageName::ObjectPathToPackageName(NormalizedPackageName);
|
|
FPackagePath PackagePath = FPackagePath::FromPackageNameChecked(NormalizedPackageName);
|
|
|
|
if (FPackageName::DoesPackageExist(PackagePath, &PackagePath))
|
|
{
|
|
CurrentState = ECurrentState::Loading;
|
|
FWorldNotifyStreamingLevelLoading::Started(PersistentWorld);
|
|
|
|
ULevel::StreamedLevelsOwningWorld.Add(DesiredPackageName, PersistentWorld);
|
|
UWorld::WorldTypePreLoadMap.FindOrAdd(DesiredPackageName) = PersistentWorld->WorldType;
|
|
|
|
// Kick off async load request.
|
|
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "RequestLevel - " ) + DesiredPackageName.ToString() )) );
|
|
TRACE_BOOKMARK(TEXT("RequestLevel - %s"), *DesiredPackageName.ToString());
|
|
|
|
FLinkerInstancingContext* InstancingContextPtr = nullptr;
|
|
#if WITH_EDITOR
|
|
FLinkerInstancingContext InstancingContext;
|
|
if (DesiredPackageName != NAME_None && PackagePath.GetPackageFName() != DesiredPackageName)
|
|
{
|
|
// When loading an instanced package we want to avoid it being processed as an asset so we make sure to set it RF_Transient.
|
|
// If the package is not created here, it will get created by the LoadPackageAsync call.
|
|
// Gameworld packages (PIE) are already ignored so we can let LoadPackageAsync do its job.
|
|
if (!bIsGameWorld)
|
|
{
|
|
UPackage* NewPackage = CreatePackage(*DesiredPackageName.ToString());
|
|
NewPackage->SetFlags(RF_Transient);
|
|
}
|
|
|
|
// When loading an instanced package we need to build an instancing context in case non external actors part of the level are
|
|
// pulling on external actors.
|
|
FString ExternalActorsPath = ULevel::GetExternalActorsPath(PackagePath.GetPackageName());
|
|
TArray<FString> ActorPackageNames = ULevel::GetOnDiskExternalActorPackages(ExternalActorsPath);
|
|
|
|
InstancingContextPtr = &InstancingContext;
|
|
for (const FString& ActorPackageName : ActorPackageNames)
|
|
{
|
|
const FString InstancedName = ULevel::GetExternalActorPackageInstanceName(DesiredPackageName.ToString(), ActorPackageName);
|
|
InstancingContext.AddMapping(FName(*ActorPackageName), FName(*InstancedName));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
LoadPackageAsync(PackagePath, DesiredPackageName, FLoadPackageAsyncDelegate::CreateUObject(this, &ULevelStreaming::AsyncLevelLoadComplete), PackageFlags, PIEInstanceID, GetPriority(), InstancingContextPtr);
|
|
|
|
// streamingServer: server loads everything?
|
|
// Editor immediately blocks on load and we also block if background level streaming is disabled.
|
|
if (BlockPolicy == AlwaysBlock || (ShouldBeAlwaysLoaded() && BlockPolicy != NeverBlock))
|
|
{
|
|
if (IsAsyncLoading())
|
|
{
|
|
UE_LOG(LogStreaming, Display, TEXT("ULevelStreaming::RequestLevel(%s) is flushing async loading"), *DesiredPackageName.ToString());
|
|
}
|
|
|
|
// Finish all async loading.
|
|
FlushAsyncLoading();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStreaming, Error,TEXT("Couldn't find file for package %s."), *PackagePath.GetDebugName());
|
|
CurrentState = ECurrentState::FailedToLoad;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ULevelStreaming::AsyncLevelLoadComplete(const FName& InPackageName, UPackage* InLoadedPackage, EAsyncLoadingResult::Type Result)
|
|
{
|
|
CurrentState = ECurrentState::LoadedNotVisible;
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
if (World->GetStreamingLevels().Contains(this))
|
|
{
|
|
FWorldNotifyStreamingLevelLoading::Finished(World);
|
|
}
|
|
}
|
|
|
|
if (InLoadedPackage)
|
|
{
|
|
UPackage* LevelPackage = InLoadedPackage;
|
|
|
|
// Try to find a UWorld object in the level package.
|
|
UWorld* World = UWorld::FindWorldInPackage(LevelPackage);
|
|
|
|
if (World)
|
|
{
|
|
if (ULevel* Level = World->PersistentLevel)
|
|
{
|
|
PrepareLoadedLevel(Level, LevelPackage, GetOutermost()->GetPIEInstanceID());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevelStreaming, Warning, TEXT("Couldn't find ULevel object in package '%s'"), *InPackageName.ToString() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No world in this package
|
|
LevelPackage->ClearPackageFlags(PKG_ContainsMap);
|
|
|
|
// There could have been a redirector in the package. Attempt to follow it.
|
|
UObjectRedirector* WorldRedirector = nullptr;
|
|
UWorld* DestinationWorld = UWorld::FollowWorldRedirectorInPackage(LevelPackage, &WorldRedirector);
|
|
if (DestinationWorld)
|
|
{
|
|
// To follow the world redirector for level streaming...
|
|
// 1) Update all globals that refer to the redirector package by name
|
|
// 2) Update the PackageNameToLoad to refer to the new package location
|
|
// 3) If the package name to load was the same as the destination package name...
|
|
// ... update the package name to the new package and let the next RequestLevel try this process again.
|
|
// If the package name to load was different...
|
|
// ... it means the specified package name was explicit and we will just load from another file.
|
|
|
|
FName OldDesiredPackageName = InPackageName;
|
|
TWeakObjectPtr<UWorld>* OwningWorldPtr = ULevel::StreamedLevelsOwningWorld.Find(OldDesiredPackageName);
|
|
UWorld* OwningWorld = OwningWorldPtr ? OwningWorldPtr->Get() : NULL;
|
|
ULevel::StreamedLevelsOwningWorld.Remove(OldDesiredPackageName);
|
|
|
|
// Try again with the destination package to load.
|
|
// IMPORTANT: check this BEFORE changing PackageNameToLoad, otherwise you wont know if the package name was supposed to be different.
|
|
const bool bLoadingIntoDifferentPackage = (GetWorldAssetPackageFName() != PackageNameToLoad) && (PackageNameToLoad != NAME_None);
|
|
|
|
// ... now set PackageNameToLoad
|
|
PackageNameToLoad = DestinationWorld->GetOutermost()->GetFName();
|
|
|
|
if ( PackageNameToLoad != OldDesiredPackageName )
|
|
{
|
|
EWorldType::Type* OldPackageWorldType = UWorld::WorldTypePreLoadMap.Find(OldDesiredPackageName);
|
|
if ( OldPackageWorldType )
|
|
{
|
|
UWorld::WorldTypePreLoadMap.FindOrAdd(PackageNameToLoad) = *OldPackageWorldType;
|
|
UWorld::WorldTypePreLoadMap.Remove(OldDesiredPackageName);
|
|
}
|
|
}
|
|
|
|
// Now determine if we are loading into the package explicitly or if it is okay to just load the other package.
|
|
if ( bLoadingIntoDifferentPackage )
|
|
{
|
|
// Loading into a new custom package explicitly. Load the destination world directly into the package.
|
|
// Detach the linker to load from a new file into the same package.
|
|
FLinkerLoad* PackageLinker = FLinkerLoad::FindExistingLinkerForPackage(LevelPackage);
|
|
if (PackageLinker)
|
|
{
|
|
PackageLinker->Detach();
|
|
DeleteLoader(PackageLinker);
|
|
PackageLinker = nullptr;
|
|
}
|
|
|
|
// Make sure the redirector is not in the way of the new world.
|
|
// Pass NULL as the name to make a new unique name and GetTransientPackage() for the outer to remove it from the package.
|
|
WorldRedirector->Rename(NULL, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_ForceNoResetLoaders | REN_NonTransactional);
|
|
|
|
// Change the loaded world's type back to inactive since it won't be used.
|
|
DestinationWorld->WorldType = EWorldType::Inactive;
|
|
}
|
|
else
|
|
{
|
|
// Loading the requested package normally. Fix up the destination world then update the requested package to the destination.
|
|
if (OwningWorld)
|
|
{
|
|
if (DestinationWorld->PersistentLevel)
|
|
{
|
|
DestinationWorld->PersistentLevel->OwningWorld = OwningWorld;
|
|
}
|
|
|
|
// In some cases, BSP render data is not created because the OwningWorld was not set correctly.
|
|
// Regenerate that render data here
|
|
DestinationWorld->PersistentLevel->InvalidateModelSurface();
|
|
DestinationWorld->PersistentLevel->CommitModelSurfaces();
|
|
}
|
|
|
|
SetWorldAsset(DestinationWorld);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (Result == EAsyncLoadingResult::Canceled)
|
|
{
|
|
// Cancel level streaming
|
|
CurrentState = ECurrentState::Unloaded;
|
|
SetShouldBeLoaded(false);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLevelStreaming, Warning, TEXT("Failed to load package '%s'"), *InPackageName.ToString() );
|
|
|
|
CurrentState = ECurrentState::FailedToLoad;
|
|
SetShouldBeLoaded(false);
|
|
}
|
|
|
|
// Clean up the world type list and owning world list now that PostLoad has occurred
|
|
UWorld::WorldTypePreLoadMap.Remove(InPackageName);
|
|
ULevel::StreamedLevelsOwningWorld.Remove(InPackageName);
|
|
|
|
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "RequestLevelComplete - " ) + InPackageName.ToString() )) );
|
|
TRACE_BOOKMARK(TEXT("RequestLevelComplete - %s"), *InPackageName.ToString());
|
|
}
|
|
|
|
bool ULevelStreaming::IsLevelVisible() const
|
|
{
|
|
return LoadedLevel != NULL && LoadedLevel->bIsVisible;
|
|
}
|
|
|
|
bool ULevelStreaming::IsStreamingStatePending() const
|
|
{
|
|
UWorld* PersistentWorld = GetWorld();
|
|
if (PersistentWorld)
|
|
{
|
|
if (IsLevelLoaded() == ShouldBeLoaded() &&
|
|
(IsLevelVisible() == ShouldBeVisible() || !ShouldBeLoaded())) // visibility state does not matter if sub-level set to be unloaded
|
|
{
|
|
const FName DesiredPackageName = PersistentWorld->IsGameWorld() ? GetLODPackageName() : GetWorldAssetPackageFName();
|
|
if (!LoadedLevel || CachedLoadedLevelPackageName == DesiredPackageName)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULevelStreaming::SetIsRequestingUnloadAndRemoval(const bool bInIsRequestingUnloadAndRemoval)
|
|
{
|
|
if (bInIsRequestingUnloadAndRemoval != bIsRequestingUnloadAndRemoval)
|
|
{
|
|
bIsRequestingUnloadAndRemoval = bInIsRequestingUnloadAndRemoval;
|
|
// Only need to do this if setting to true because if we weren't already being considered and in a transitional state
|
|
// we would have already been removed so it would be irrelevant
|
|
if (bInIsRequestingUnloadAndRemoval)
|
|
{
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULevelStreaming::SetShouldBeVisibleInEditor(const bool bInShouldBeVisibleInEditor)
|
|
{
|
|
if (bInShouldBeVisibleInEditor != bShouldBeVisibleInEditor)
|
|
{
|
|
bShouldBeVisibleInEditor = bInShouldBeVisibleInEditor;
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ULevelStreaming* ULevelStreaming::CreateInstance(const FString& InstanceUniqueName)
|
|
{
|
|
ULevelStreaming* StreamingLevelInstance = nullptr;
|
|
|
|
UWorld* InWorld = GetWorld();
|
|
if (InWorld)
|
|
{
|
|
// Create instance long package name
|
|
FString InstanceShortPackageName = InWorld->StreamingLevelsPrefix + FPackageName::GetShortName(InstanceUniqueName);
|
|
FString InstancePackagePath = FPackageName::GetLongPackagePath(GetWorldAssetPackageName()) + TEXT("/");
|
|
FName InstanceUniquePackageName = FName(*(InstancePackagePath + InstanceShortPackageName));
|
|
|
|
// check if instance name is unique among existing streaming level objects
|
|
const bool bUniqueName = (InWorld->GetStreamingLevels().IndexOfByPredicate(ULevelStreaming::FPackageNameMatcher(InstanceUniquePackageName)) == INDEX_NONE);
|
|
|
|
if (bUniqueName)
|
|
{
|
|
StreamingLevelInstance = NewObject<ULevelStreaming>(InWorld, GetClass(), NAME_None, RF_Transient, NULL);
|
|
// new level streaming instance will load the same map package as this object
|
|
StreamingLevelInstance->PackageNameToLoad = ((PackageNameToLoad == NAME_None) ? GetWorldAssetPackageFName() : PackageNameToLoad);
|
|
// under a provided unique name
|
|
StreamingLevelInstance->SetWorldAssetByPackageName(InstanceUniquePackageName);
|
|
StreamingLevelInstance->SetShouldBeLoaded(false);
|
|
StreamingLevelInstance->SetShouldBeVisible(false);
|
|
StreamingLevelInstance->LevelTransform = LevelTransform;
|
|
|
|
// add a new instance to streaming level list
|
|
InWorld->AddStreamingLevel(StreamingLevelInstance);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStreaming, Warning, TEXT("Provided streaming level instance name is not unique: %s"), *InstanceUniquePackageName.ToString());
|
|
}
|
|
}
|
|
|
|
return StreamingLevelInstance;
|
|
}
|
|
|
|
void ULevelStreaming::BroadcastLevelLoadedStatus(UWorld* PersistentWorld, FName LevelPackageName, bool bLoaded)
|
|
{
|
|
for (ULevelStreaming* StreamingLevel : PersistentWorld->GetStreamingLevels())
|
|
{
|
|
if (StreamingLevel->GetWorldAssetPackageFName() == LevelPackageName)
|
|
{
|
|
if (bLoaded)
|
|
{
|
|
StreamingLevel->OnLevelLoaded.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
StreamingLevel->OnLevelUnloaded.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::BroadcastLevelVisibleStatus(UWorld* PersistentWorld, FName LevelPackageName, bool bVisible)
|
|
{
|
|
TArray<ULevelStreaming*, TInlineAllocator<1>> LevelsToBroadcast;
|
|
|
|
for (ULevelStreaming* StreamingLevel : PersistentWorld->GetStreamingLevels())
|
|
{
|
|
if (StreamingLevel->GetWorldAssetPackageFName() == LevelPackageName)
|
|
{
|
|
LevelsToBroadcast.Add(StreamingLevel);
|
|
}
|
|
}
|
|
|
|
for (ULevelStreaming* StreamingLevel : LevelsToBroadcast)
|
|
{
|
|
if (bVisible)
|
|
{
|
|
StreamingLevel->OnLevelShown.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
StreamingLevel->OnLevelHidden.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::SetWorldAsset(const TSoftObjectPtr<UWorld>& NewWorldAsset)
|
|
{
|
|
if (WorldAsset != NewWorldAsset)
|
|
{
|
|
WorldAsset = NewWorldAsset;
|
|
bHasCachedWorldAssetPackageFName = false;
|
|
bHasCachedLoadedLevelPackageName = false;
|
|
|
|
if (CurrentState == ECurrentState::FailedToLoad)
|
|
{
|
|
CurrentState = ECurrentState::Unloaded;
|
|
}
|
|
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
FString ULevelStreaming::GetWorldAssetPackageName() const
|
|
{
|
|
return GetWorldAssetPackageFName().ToString();
|
|
}
|
|
|
|
FName ULevelStreaming::GetWorldAssetPackageFName() const
|
|
{
|
|
if (!bHasCachedWorldAssetPackageFName)
|
|
{
|
|
CachedWorldAssetPackageFName = WorldAsset.ToSoftObjectPath().GetLongPackageFName();
|
|
bHasCachedWorldAssetPackageFName = true;
|
|
}
|
|
return CachedWorldAssetPackageFName;
|
|
}
|
|
|
|
FName ULevelStreaming::GetLoadedLevelPackageName() const
|
|
{
|
|
if( !bHasCachedLoadedLevelPackageName )
|
|
{
|
|
CachedLoadedLevelPackageName = (LoadedLevel ? LoadedLevel->GetOutermost()->GetFName() : NAME_None);
|
|
bHasCachedLoadedLevelPackageName = true;
|
|
}
|
|
|
|
return CachedLoadedLevelPackageName;
|
|
}
|
|
|
|
void ULevelStreaming::SetWorldAssetByPackageName(FName InPackageName)
|
|
{
|
|
// Need to strip PIE prefix from object name, only the package has it
|
|
const FString TargetWorldPackageName = InPackageName.ToString();
|
|
const FString TargetWorldObjectName = UWorld::RemovePIEPrefix(FPackageName::GetLongPackageAssetName(TargetWorldPackageName));
|
|
TSoftObjectPtr<UWorld> NewWorld;
|
|
NewWorld = FString::Printf(TEXT("%s.%s"), *TargetWorldPackageName, *TargetWorldObjectName);
|
|
SetWorldAsset(NewWorld);
|
|
}
|
|
|
|
void ULevelStreaming::RenameForPIE(int32 PIEInstanceID, bool bKeepWorldAssetName)
|
|
{
|
|
const UWorld* const World = GetWorld();
|
|
|
|
// Apply PIE prefix so this level references
|
|
if (!WorldAsset.IsNull())
|
|
{
|
|
// Store original name
|
|
if (PackageNameToLoad == NAME_None)
|
|
{
|
|
FString NonPrefixedName = UWorld::StripPIEPrefixFromPackageName(
|
|
GetWorldAssetPackageName(),
|
|
UWorld::BuildPIEPackagePrefix(PIEInstanceID));
|
|
PackageNameToLoad = FName(*NonPrefixedName);
|
|
}
|
|
FName PlayWorldStreamingPackageName = FName(*UWorld::ConvertToPIEPackageName(GetWorldAssetPackageName(), PIEInstanceID));
|
|
FSoftObjectPath::AddPIEPackageName(PlayWorldStreamingPackageName);
|
|
if (bKeepWorldAssetName)
|
|
{
|
|
SetWorldAsset(TSoftObjectPtr<UWorld>(FString::Printf(TEXT("%s.%s"), *PlayWorldStreamingPackageName.ToString(), *FPackageName::ObjectPathToObjectName(WorldAsset.ToString()))));
|
|
}
|
|
else
|
|
{
|
|
SetWorldAssetByPackageName(PlayWorldStreamingPackageName);
|
|
}
|
|
|
|
NetDriverRenameStreamingLevelPackageForPIE(World, PackageNameToLoad);
|
|
}
|
|
|
|
// Rename LOD levels if any
|
|
if (LODPackageNames.Num() > 0)
|
|
{
|
|
LODPackageNamesToLoad.Reset(LODPackageNames.Num());
|
|
for (FName& LODPackageName : LODPackageNames)
|
|
{
|
|
// Store LOD level original package name
|
|
LODPackageNamesToLoad.Add(LODPackageName);
|
|
// Apply PIE prefix to package name
|
|
const FName NonPrefixedLODPackageName = LODPackageName;
|
|
LODPackageName = FName(*UWorld::ConvertToPIEPackageName(LODPackageName.ToString(), PIEInstanceID));
|
|
FSoftObjectPath::AddPIEPackageName(LODPackageName);
|
|
|
|
NetDriverRenameStreamingLevelPackageForPIE(World, NonPrefixedLODPackageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::SetPriority(const int32 NewPriority)
|
|
{
|
|
if (NewPriority != StreamingPriority)
|
|
{
|
|
StreamingPriority = NewPriority;
|
|
|
|
if (CurrentState != ECurrentState::Removed && CurrentState != ECurrentState::FailedToLoad)
|
|
{
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelPriority(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::SetLevelLODIndex(const int32 LODIndex)
|
|
{
|
|
if (LODIndex != LevelLODIndex)
|
|
{
|
|
LevelLODIndex = LODIndex;
|
|
|
|
if (CurrentState == ECurrentState::FailedToLoad)
|
|
{
|
|
CurrentState = ECurrentState::Unloaded;
|
|
}
|
|
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::SetShouldBeVisible(const bool bInShouldBeVisible)
|
|
{
|
|
if (bInShouldBeVisible != bShouldBeVisible)
|
|
{
|
|
bShouldBeVisible = bInShouldBeVisible;
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULevelStreaming::SetShouldBeLoaded(const bool bInShouldBeLoaded)
|
|
{
|
|
}
|
|
|
|
bool ULevelStreaming::ShouldBeVisible() const
|
|
{
|
|
if( GetWorld()->IsGameWorld() )
|
|
{
|
|
// Game and play in editor viewport codepath.
|
|
return bShouldBeVisible && ShouldBeLoaded();
|
|
}
|
|
#if WITH_EDITORONLY_DATA
|
|
// Editor viewport codepath.
|
|
return bShouldBeVisibleInEditor;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
FBox ULevelStreaming::GetStreamingVolumeBounds()
|
|
{
|
|
FBox Bounds(ForceInit);
|
|
|
|
// Iterate over each volume associated with this LevelStreaming object
|
|
for(int32 VolIdx=0; VolIdx<EditorStreamingVolumes.Num(); VolIdx++)
|
|
{
|
|
ALevelStreamingVolume* StreamingVol = EditorStreamingVolumes[VolIdx];
|
|
if(StreamingVol && StreamingVol->GetBrushComponent())
|
|
{
|
|
Bounds += StreamingVol->GetBrushComponent()->BrushBodySetup->AggGeom.CalcAABB(StreamingVol->GetBrushComponent()->GetComponentTransform());
|
|
}
|
|
}
|
|
|
|
return Bounds;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULevelStreaming::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
FProperty* OutermostProperty = PropertyChangedEvent.Property;
|
|
if ( OutermostProperty != NULL )
|
|
{
|
|
const FName PropertyName = OutermostProperty->GetFName();
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, LevelTransform))
|
|
{
|
|
GetWorld()->UpdateLevelStreaming();
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, EditorStreamingVolumes))
|
|
{
|
|
RemoveStreamingVolumeDuplicates();
|
|
|
|
// Update levels references in each streaming volume
|
|
for (TActorIterator<ALevelStreamingVolume> It(GetWorld()); It; ++It)
|
|
{
|
|
(*It)->UpdateStreamingLevelsRefs();
|
|
}
|
|
}
|
|
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, LevelColor))
|
|
{
|
|
// Make sure the level's Level Color change is applied immediately by reregistering the
|
|
// components of the actor's in the level
|
|
if (LoadedLevel != nullptr)
|
|
{
|
|
LoadedLevel->MarkLevelComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, WorldAsset))
|
|
{
|
|
bHasCachedWorldAssetPackageFName = false;
|
|
bHasCachedLoadedLevelPackageName = false;
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, bIsStatic))
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
const ELevelCollectionType NewCollectionType = bIsStatic ? ELevelCollectionType::StaticLevels : ELevelCollectionType::DynamicSourceLevels;
|
|
FLevelCollection* PreviousCollection = LoadedLevel->GetCachedLevelCollection();
|
|
|
|
if (PreviousCollection && PreviousCollection->GetType() != NewCollectionType)
|
|
{
|
|
PreviousCollection->RemoveLevel(LoadedLevel);
|
|
|
|
UWorld* World = GetWorld();
|
|
FLevelCollection& LC = World->FindOrAddCollectionByType(NewCollectionType);
|
|
LC.AddLevel(LoadedLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
void ULevelStreaming::RemoveStreamingVolumeDuplicates()
|
|
{
|
|
for (int32 VolumeIdx = EditorStreamingVolumes.Num()-1; VolumeIdx >= 0; VolumeIdx--)
|
|
{
|
|
ALevelStreamingVolume* Volume = EditorStreamingVolumes[VolumeIdx];
|
|
if (Volume) // Allow duplicate null entries, for array editor convenience
|
|
{
|
|
int32 DuplicateIdx = EditorStreamingVolumes.Find(Volume);
|
|
check(DuplicateIdx != INDEX_NONE);
|
|
if (DuplicateIdx != VolumeIdx)
|
|
{
|
|
EditorStreamingVolumes.RemoveAt(VolumeIdx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
ALevelScriptActor* ULevelStreaming::GetLevelScriptActor()
|
|
{
|
|
if (LoadedLevel)
|
|
{
|
|
return LoadedLevel->GetLevelScriptActor();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool ULevelStreaming::IsValidStreamingLevel() const
|
|
{
|
|
const bool PIESession = GetWorld()->WorldType == EWorldType::PIE || GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor);
|
|
if (!PIESession && !WorldAsset.IsNull())
|
|
{
|
|
FName WorldPackageName = GetWorldAssetPackageFName();
|
|
FPackagePath WorldPackagePath;
|
|
return FPackagePath::TryFromPackageName(WorldPackageName, /* Out*/ WorldPackagePath) && FPackageName::DoesPackageExist(WorldPackagePath);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
EStreamingStatus ULevelStreaming::GetLevelStreamingStatus() const
|
|
{
|
|
if (CurrentState == ECurrentState::FailedToLoad)
|
|
{
|
|
return LEVEL_FailedToLoad;
|
|
}
|
|
else if (CurrentState == ECurrentState::MakingInvisible)
|
|
{
|
|
return LEVEL_MakingInvisible;
|
|
}
|
|
else if (LoadedLevel)
|
|
{
|
|
if (CurrentState == ECurrentState::LoadedVisible)
|
|
{
|
|
return LEVEL_Visible;
|
|
}
|
|
else if ((CurrentState == ECurrentState::MakingVisible) && (GetWorld()->GetCurrentLevelPendingVisibility() == LoadedLevel))
|
|
{
|
|
return LEVEL_MakingVisible;
|
|
}
|
|
return LEVEL_Loaded;
|
|
}
|
|
else
|
|
{
|
|
if (CurrentState == ECurrentState::Loading)
|
|
{
|
|
return LEVEL_Loading;
|
|
}
|
|
|
|
check(CurrentState == ECurrentState::Removed || CurrentState == ECurrentState::Unloaded);
|
|
// See whether the level's world object is still around.
|
|
UPackage* LevelPackage = FindObjectFast<UPackage>(nullptr, GetWorldAssetPackageFName());
|
|
UWorld* LevelWorld = LevelPackage ? UWorld::FindWorldInPackage(LevelPackage) : nullptr;
|
|
return LevelWorld ? LEVEL_UnloadedButStillAround : LEVEL_Unloaded;
|
|
}
|
|
}
|
|
|
|
/** Utility that gets a color for a particular level status */
|
|
FColor ULevelStreaming::GetLevelStreamingStatusColor(EStreamingStatus Status)
|
|
{
|
|
switch (Status)
|
|
{
|
|
case LEVEL_Unloaded: return FColor::Red;
|
|
case LEVEL_UnloadedButStillAround: return FColor::Purple;
|
|
case LEVEL_Loading: return FColor::Yellow;
|
|
case LEVEL_Loaded: return FColor::Cyan;
|
|
case LEVEL_MakingVisible: return FColor::Blue;
|
|
case LEVEL_Visible: return FColor::Green;
|
|
case LEVEL_Preloading: return FColor::Magenta;
|
|
case LEVEL_FailedToLoad: return FColorList::Maroon;
|
|
case LEVEL_MakingInvisible: return FColorList::Orange;
|
|
default: return FColor::White;
|
|
};
|
|
}
|
|
|
|
const TCHAR* ULevelStreaming::GetLevelStreamingStatusDisplayName(EStreamingStatus Status)
|
|
{
|
|
switch (Status)
|
|
{
|
|
case LEVEL_Unloaded: return TEXT("Unloaded");
|
|
case LEVEL_UnloadedButStillAround: return TEXT("Unloaded Still Around");
|
|
case LEVEL_Loading: return TEXT("Loading");
|
|
case LEVEL_Loaded: return TEXT("Loaded Not Visible");
|
|
case LEVEL_MakingVisible: return TEXT("Making Visible");
|
|
case LEVEL_Visible: return TEXT("Loaded Visible");
|
|
case LEVEL_Preloading: return TEXT("Preloading");
|
|
case LEVEL_FailedToLoad: return TEXT("Failed to Load");
|
|
case LEVEL_MakingInvisible: return TEXT("Making Invisible");
|
|
default: return TEXT("Unknown");
|
|
};
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULevelStreaming::PostEditUndo()
|
|
{
|
|
Super::PostEditUndo();
|
|
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
|
|
TOptional<FFolder::FRootObject> ULevelStreaming::GetFolderRootObject() const
|
|
{
|
|
// We consider that if either the loaded level or its world persistent level uses actor folder objects, the loaded level is the folder root object.
|
|
if (LoadedLevel)
|
|
{
|
|
if (LoadedLevel->IsUsingActorFolders() || LoadedLevel->GetWorld()->PersistentLevel->IsUsingActorFolders())
|
|
{
|
|
return FFolder::FRootObject(LoadedLevel);
|
|
}
|
|
else
|
|
{
|
|
return FFolder::GetWorldRootFolder(LoadedLevel->GetWorld()).GetRootObject();
|
|
}
|
|
}
|
|
return TOptional<FFolder::FRootObject>();
|
|
}
|
|
|
|
const FName& ULevelStreaming::GetFolderPath() const
|
|
{
|
|
return FolderPath;
|
|
}
|
|
|
|
void ULevelStreaming::SetFolderPath(const FName& InFolderPath)
|
|
{
|
|
if (FolderPath != InFolderPath)
|
|
{
|
|
Modify();
|
|
|
|
FolderPath = InFolderPath;
|
|
|
|
// @TODO: Should this be broadcasted through the editor, similar to BroadcastLevelActorFolderChanged?
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
ULevelStreamingPersistent implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
ULevelStreamingPersistent::ULevelStreamingPersistent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
ULevelStreamingDynamic implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
ULevelStreamingDynamic::ULevelStreamingDynamic(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
void ULevelStreamingDynamic::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
// Initialize startup state of the streaming level
|
|
if ( GetWorld()->IsGameWorld() )
|
|
{
|
|
SetShouldBeLoaded(bInitiallyLoaded);
|
|
SetShouldBeVisible(bInitiallyVisible);
|
|
}
|
|
}
|
|
|
|
void ULevelStreamingDynamic::SetShouldBeLoaded(const bool bInShouldBeLoaded)
|
|
{
|
|
if (bInShouldBeLoaded != bShouldBeLoaded)
|
|
{
|
|
bShouldBeLoaded = bInShouldBeLoaded;
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstance(UObject* WorldContextObject, const FString LevelObjectPath, const FVector Location, const FRotator Rotation, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage)
|
|
{
|
|
bOutSuccess = false;
|
|
UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
|
if (!World)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Check whether requested map exists
|
|
// LevelObjectPath may be either an objectpath, a package name, or a file path; and packagename/object path may be a LongPackageName or ShortPackageName
|
|
// convert that flexible input to ObjectPath with a LongPackageName
|
|
FString PackageName;
|
|
FString ObjectRelativePath;
|
|
if (FPackageName::IsShortPackageName(LevelObjectPath) || FPackageName::IsValidObjectPath(LevelObjectPath))
|
|
{
|
|
FString UnusedClassName;
|
|
FString ObjectName;
|
|
FString SubObjectName;
|
|
FPackageName::SplitFullObjectPath(LevelObjectPath, UnusedClassName, PackageName, ObjectName, SubObjectName);
|
|
if (!ObjectName.IsEmpty())
|
|
{
|
|
ObjectRelativePath = FString(TEXT(".")) + ObjectName;
|
|
if (!SubObjectName.IsEmpty())
|
|
{
|
|
ObjectRelativePath += FString(SUBOBJECT_DELIMITER) + SubObjectName;
|
|
}
|
|
}
|
|
}
|
|
else if (!FPackageName::TryConvertFilenameToLongPackageName(LevelObjectPath, PackageName))
|
|
{
|
|
// An unrecognized path or format
|
|
return nullptr;
|
|
}
|
|
|
|
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
FName ExistingPackageName = AssetRegistry.GetFirstPackageByName(PackageName);
|
|
bOutSuccess = !ExistingPackageName.IsNone();
|
|
if (!bOutSuccess)
|
|
{
|
|
return nullptr;
|
|
}
|
|
FString LongPackageNameObjectPath = ExistingPackageName.ToString() + ObjectRelativePath;
|
|
|
|
return LoadLevelInstance_Internal(World, LongPackageNameObjectPath, FTransform(Rotation, Location), bOutSuccess, OptionalLevelNameOverride, OptionalLevelStreamingClass, bLoadAsTempPackage);
|
|
}
|
|
|
|
ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(UObject* WorldContextObject, const TSoftObjectPtr<UWorld> Level, const FVector Location, const FRotator Rotation, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage)
|
|
{
|
|
return LoadLevelInstanceBySoftObjectPtr(WorldContextObject, Level, FTransform(Rotation, Location), bOutSuccess, OptionalLevelNameOverride, OptionalLevelStreamingClass, bLoadAsTempPackage);
|
|
}
|
|
|
|
ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(UObject* WorldContextObject, const TSoftObjectPtr<UWorld> Level, const FTransform LevelTransform, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage)
|
|
{
|
|
bOutSuccess = false;
|
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
|
if (!World)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Check whether requested map exists, this could be very slow if LevelName is a short package name
|
|
if (Level.IsNull())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return LoadLevelInstance_Internal(World, Level.GetLongPackageName(), LevelTransform, bOutSuccess, OptionalLevelNameOverride, OptionalLevelStreamingClass, bLoadAsTempPackage);
|
|
}
|
|
|
|
ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstance_Internal(UWorld* World, const FString& LongPackageName, const FTransform LevelTransform, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage)
|
|
{
|
|
const FString PackagePath = FPackageName::GetLongPackagePath(LongPackageName);
|
|
FString ShortPackageName = FPackageName::GetShortName(LongPackageName);
|
|
UClass* LevelStreamingClass = OptionalLevelStreamingClass != nullptr ? OptionalLevelStreamingClass.Get() : ULevelStreamingDynamic::StaticClass();
|
|
|
|
if (ShortPackageName.StartsWith(World->StreamingLevelsPrefix))
|
|
{
|
|
ShortPackageName.RightChopInline(World->StreamingLevelsPrefix.Len(), false);
|
|
}
|
|
|
|
// Remove PIE prefix if it's there before we actually load the level
|
|
const FString OnDiskPackageName = PackagePath + TEXT("/") + ShortPackageName;
|
|
|
|
// Determine loaded package name
|
|
TStringBuilder<512> LevelPackageNameStrBuilder;
|
|
if (bLoadAsTempPackage)
|
|
{
|
|
LevelPackageNameStrBuilder.Append(TEXT("/Temp"));
|
|
}
|
|
LevelPackageNameStrBuilder.Append(PackagePath);
|
|
LevelPackageNameStrBuilder.Append(TEXT("/"));
|
|
|
|
bool bNeedsUniqueTest = false;
|
|
if (OptionalLevelNameOverride.IsEmpty())
|
|
{
|
|
LevelPackageNameStrBuilder.Append(ShortPackageName);
|
|
LevelPackageNameStrBuilder.Append(TEXT("_LevelInstance_"));
|
|
LevelPackageNameStrBuilder.Append(FString::FromInt(++UniqueLevelInstanceId));
|
|
}
|
|
else
|
|
{
|
|
// Use the supplied suffix, which is expected to result in a unique package name but we have to check if it is not.
|
|
LevelPackageNameStrBuilder.Append(OptionalLevelNameOverride);
|
|
bNeedsUniqueTest = true;
|
|
}
|
|
|
|
const FString LevelPackageNameStr(LevelPackageNameStrBuilder.ToString());
|
|
const FName UnmodifiedLevelPackageName = FName(*LevelPackageNameStr);
|
|
#if WITH_EDITOR
|
|
const bool bIsPlayInEditor = World->IsPlayInEditor();
|
|
int32 PIEInstance = INDEX_NONE;
|
|
if (bIsPlayInEditor)
|
|
{
|
|
const FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
|
|
PIEInstance = WorldContext.PIEInstance;
|
|
}
|
|
#endif
|
|
if (bNeedsUniqueTest)
|
|
{
|
|
FName ModifiedLevelPackageName = UnmodifiedLevelPackageName;
|
|
#if WITH_EDITOR
|
|
if (bIsPlayInEditor)
|
|
{
|
|
ModifiedLevelPackageName = FName(*UWorld::ConvertToPIEPackageName(LevelPackageNameStr, PIEInstance));
|
|
}
|
|
#endif
|
|
if (World->GetStreamingLevels().ContainsByPredicate([&ModifiedLevelPackageName](ULevelStreaming* LS) { return LS && LS->GetWorldAssetPackageFName() == ModifiedLevelPackageName; }))
|
|
{
|
|
// The streaming level already exists, error and return.
|
|
UE_LOG(LogLevelStreaming, Error, TEXT("LoadLevelInstance called with a name that already exists, returning nullptr. LevelPackageName:%s"), *ModifiedLevelPackageName.ToString());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Setup streaming level object that will load specified map
|
|
ULevelStreamingDynamic* StreamingLevel = NewObject<ULevelStreamingDynamic>(World, LevelStreamingClass, NAME_None, RF_Transient, NULL);
|
|
StreamingLevel->SetWorldAssetByPackageName(UnmodifiedLevelPackageName);
|
|
#if WITH_EDITOR
|
|
if (bIsPlayInEditor)
|
|
{
|
|
// Necessary for networking in PIE
|
|
StreamingLevel->RenameForPIE(PIEInstance);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
StreamingLevel->LevelColor = FColor::MakeRandomColor();
|
|
StreamingLevel->SetShouldBeLoaded(true);
|
|
StreamingLevel->SetShouldBeVisible(true);
|
|
StreamingLevel->bShouldBlockOnLoad = false;
|
|
StreamingLevel->bInitiallyLoaded = true;
|
|
StreamingLevel->bInitiallyVisible = true;
|
|
// Transform
|
|
StreamingLevel->LevelTransform = LevelTransform;
|
|
// Map to Load
|
|
StreamingLevel->PackageNameToLoad = FName(*OnDiskPackageName);
|
|
|
|
// Add the new level to world.
|
|
World->AddStreamingLevel(StreamingLevel);
|
|
|
|
bOutSuccess = true;
|
|
return StreamingLevel;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
ULevelStreamingAlwaysLoaded implementation.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
ULevelStreamingAlwaysLoaded::ULevelStreamingAlwaysLoaded(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
SetShouldBeVisible(true);
|
|
}
|
|
|
|
void ULevelStreamingAlwaysLoaded::GetPrestreamPackages(TArray<UObject*>& OutPrestream)
|
|
{
|
|
OutPrestream.Add(GetLoadedLevel()); // Nulls will be ignored later
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|