Files
UnrealEngineUWP/Engine/Source/Runtime/Engine/Private/LevelUtils.cpp
Mattias Hornlund 822b1c80e5 Implemented optional support for assigning a TransactionId for LevelStatusUpdate and ServerUpdateLevelVisibility requests. This is used in multiplayer to allow server to rely on levelvisibility status reported by clients to know what data to replicate to clients.
Behavior is controlled by two CVars:
LevelStreaming.ShouldClientUseMakingInvisibleTransactionRequest
- if enabled client will send a request to server and wait for an ack before making a streaming level invisible

LevelStreaming.ShouldServerUseMakingVisibleTransactionRequest
- If enabled server will associate a transaction id to each call to ClientUpdateLevelStreamingStatus and wait for the response from the client of that particular request before updating LevelVisibility for replicated data. This is to solve some issues where we might have multiple requests in flight.

By default both CVars are disabled.

#jira UE-127403
#rb Peter.Engstrom,Richard.Malo,Brian.Bekich
#preflight 627a0f0e057d42a0e4322192

[CL 20119735 by Mattias Hornlund in ue5-main branch]
2022-05-10 03:19:36 -04:00

487 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelUtils.h"
#include "Engine/Engine.h"
#include "Engine/LevelStreaming.h"
#include "HAL/FileManager.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "EditorSupportDelegates.h"
#include "EngineGlobals.h"
#include "Misc/FeedbackContext.h"
#include "GameFramework/WorldSettings.h"
#include "Components/ModelComponent.h"
#if WITH_EDITOR
#include "ScopedTransaction.h"
#endif
#define LOCTEXT_NAMESPACE "LevelUtils"
#if WITH_EDITOR
// Structure to hold the state of the Level file on disk, the goal is to query it only one time per frame.
struct FLevelReadOnlyData
{
FLevelReadOnlyData()
:IsReadOnly(false)
,LastUpdateTime(-1.0f)
{}
/** the current level file state */
bool IsReadOnly;
/** Last time when the level file state was update */
float LastUpdateTime;
};
// Map to link the level data with a level
static TMap<ULevel*, FLevelReadOnlyData> LevelReadOnlyCache;
#endif
/////////////////////////////////////////////////////////////////////////////////////////
//
// FindStreamingLevel methods.
//
/////////////////////////////////////////////////////////////////////////////////////////
#if WITH_EDITOR
bool FLevelUtils::bMovingLevel = false;
bool FLevelUtils::bApplyingLevelTransform = false;
#endif
ULevelStreaming* FLevelUtils::FindStreamingLevel(const ULevel* Level)
{
return ULevelStreaming::FindStreamingLevel(Level);
}
ULevelStreaming* FLevelUtils::FindStreamingLevel(UWorld* InWorld, const FName PackageName)
{
ULevelStreaming* MatchingLevel = NULL;
if (InWorld && !PackageName.IsNone())
{
for (ULevelStreaming* CurStreamingLevel : InWorld->GetStreamingLevels())
{
if (CurStreamingLevel && CurStreamingLevel->GetWorldAssetPackageFName() == PackageName)
{
MatchingLevel = CurStreamingLevel;
break;
}
}
}
return MatchingLevel;
}
ULevelStreaming* FLevelUtils::FindStreamingLevel(UWorld* InWorld, const TCHAR* InPackageName)
{
return FindStreamingLevel(InWorld, FName(InPackageName));
}
/////////////////////////////////////////////////////////////////////////////////////////
//
// Level locking/unlocking.
//
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns true if the specified level is locked for edit, false otherwise.
*
* @param Level The level to query.
* @return true if the level is locked, false otherwise.
*/
#if WITH_EDITOR
bool FLevelUtils::IsLevelLocked(ULevel* Level)
{
//We should not check file status on disk if we are not running the editor
// Don't permit spawning in read only levels if they are locked
if ( GIsEditor && !GIsEditorLoadingPackage )
{
if ( GEngine && GEngine->bLockReadOnlyLevels )
{
if (!LevelReadOnlyCache.Contains(Level))
{
LevelReadOnlyCache.Add(Level, FLevelReadOnlyData());
}
check(LevelReadOnlyCache.Contains(Level));
FLevelReadOnlyData &LevelData = LevelReadOnlyCache[Level];
//Make sure we test if the level file on disk is readonly only once a frame,
//when the frame time get updated.
if (LevelData.LastUpdateTime < Level->OwningWorld->GetRealTimeSeconds())
{
LevelData.LastUpdateTime = Level->OwningWorld->GetRealTimeSeconds();
//If we dont find package we dont consider it as readonly
LevelData.IsReadOnly = false;
const UPackage* pPackage = Level->GetOutermost();
if (pPackage)
{
FString PackageFileName;
if (FPackageName::DoesPackageExist(pPackage->GetName(), &PackageFileName))
{
LevelData.IsReadOnly = IFileManager::Get().IsReadOnly(*PackageFileName);
}
}
}
if (LevelData.IsReadOnly)
{
return true;
}
}
}
// PIE levels and transient move levels are usually never locked.
if ( Level->RootPackageHasAnyFlags(PKG_PlayInEditor) || Level->GetName() == TEXT("TransLevelMoveBuffer") )
{
return false;
}
ULevelStreaming* StreamingLevel = FindStreamingLevel( Level );
if ( StreamingLevel != NULL )
{
return StreamingLevel->bLocked;
}
else
{
return Level->bLocked;
}
}
bool FLevelUtils::IsLevelLocked( AActor* Actor )
{
return Actor != NULL && !Actor->IsTemplate() && Actor->GetLevel() != NULL && IsLevelLocked(Actor->GetLevel());
}
/**
* Sets a level's edit lock.
*
* @param Level The level to modify.
*/
void FLevelUtils::ToggleLevelLock(ULevel* Level)
{
if ( !Level )
{
return;
}
ULevelStreaming* StreamingLevel = FindStreamingLevel( Level );
if ( StreamingLevel != NULL )
{
// We need to set the RF_Transactional to make a streaming level serialize itself. so store the original ones, set the flag, and put the original flags back when done
EObjectFlags cachedFlags = StreamingLevel->GetFlags();
StreamingLevel->SetFlags( RF_Transactional );
StreamingLevel->Modify();
StreamingLevel->SetFlags( cachedFlags );
StreamingLevel->bLocked = !StreamingLevel->bLocked;
}
else
{
Level->Modify();
Level->bLocked = !Level->bLocked;
}
}
#endif //#if WITH_EDITOR
/////////////////////////////////////////////////////////////////////////////////////////
//
// Level loading/unloading.
//
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns true if the level is currently loaded in the editor, false otherwise.
*
* @param Level The level to query.
* @return true if the level is loaded, false otherwise.
*/
bool FLevelUtils::IsLevelLoaded(ULevel* Level)
{
if ( Level && Level->IsPersistentLevel() )
{
// The persistent level is always loaded.
return true;
}
ULevelStreaming* StreamingLevel = FindStreamingLevel( Level );
return (StreamingLevel != nullptr);
}
/////////////////////////////////////////////////////////////////////////////////////////
//
// Level visibility.
//
/////////////////////////////////////////////////////////////////////////////////////////
#if WITH_EDITORONLY_DATA
/**
* Returns true if the specified level is visible in the editor, false otherwise.
*
* @param StreamingLevel The level to query.
*/
bool FLevelUtils::IsStreamingLevelVisibleInEditor(const ULevelStreaming* StreamingLevel)
{
const bool bVisible = StreamingLevel && StreamingLevel->GetShouldBeVisibleInEditor();
return bVisible;
}
#endif
/**
* Returns true if the specified level is visible in the editor, false otherwise.
*
* @param Level The level to query.
*/
bool FLevelUtils::IsLevelVisible(const ULevel* Level)
{
if (!Level)
{
return false;
}
// P-level is specially handled
if ( Level->IsPersistentLevel() )
{
#if WITH_EDITORONLY_DATA
return !( Level->OwningWorld->PersistentLevel->GetWorldSettings()->bHiddenEdLevel );
#else
return true;
#endif
}
static const FName NAME_TransLevelMoveBuffer(TEXT("TransLevelMoveBuffer"));
if (Level->GetFName() == NAME_TransLevelMoveBuffer)
{
// The TransLevelMoveBuffer does not exist in the streaming list and is never visible
return false;
}
return Level->bIsVisible;
}
#if WITH_EDITOR
/////////////////////////////////////////////////////////////////////////////////////////
//
// Level editor transforms.
//
/////////////////////////////////////////////////////////////////////////////////////////
void FLevelUtils::SetEditorTransform(ULevelStreaming* StreamingLevel, const FTransform& Transform, bool bDoPostEditMove )
{
check(StreamingLevel);
// Check we are actually changing the value
if(StreamingLevel->LevelTransform.Equals(Transform))
{
return;
}
// Setup an Undo transaction
const FScopedTransaction LevelOffsetTransaction( LOCTEXT( "ChangeEditorLevelTransform", "Edit Level Transform" ) );
StreamingLevel->Modify();
// Ensure that all Actors are in the transaction so that their location is restored and any construction script behaviors
// based on being at a different location are correctly applied on undo/redo
if (ULevel* LoadedLevel = StreamingLevel->GetLoadedLevel())
{
for (AActor* Actor : LoadedLevel->Actors)
{
if (Actor)
{
Actor->Modify();
}
}
}
// Apply new transform
RemoveEditorTransform(StreamingLevel, false );
StreamingLevel->LevelTransform = Transform;
ApplyEditorTransform(StreamingLevel, bDoPostEditMove);
// Redraw the viewports to see this change
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
}
void FLevelUtils::ApplyEditorTransform(const ULevelStreaming* StreamingLevel, bool bDoPostEditMove, AActor* Actor)
{
check(StreamingLevel);
if (ULevel* LoadedLevel = StreamingLevel->GetLoadedLevel())
{
FApplyLevelTransformParams TransformParams(LoadedLevel, StreamingLevel->LevelTransform);
TransformParams.Actor = Actor;
TransformParams.bDoPostEditMove = bDoPostEditMove;
ApplyLevelTransform(TransformParams);
}
}
void FLevelUtils::RemoveEditorTransform(const ULevelStreaming* StreamingLevel, bool bDoPostEditMove, AActor* Actor)
{
check(StreamingLevel);
if (ULevel* LoadedLevel = StreamingLevel->GetLoadedLevel())
{
const FTransform InverseTransform = StreamingLevel->LevelTransform.Inverse();
FApplyLevelTransformParams TransformParams(LoadedLevel, InverseTransform);
TransformParams.Actor = Actor;
TransformParams.bDoPostEditMove = bDoPostEditMove;
ApplyLevelTransform(TransformParams);
}
}
void FLevelUtils::ApplyPostEditMove( ULevel* Level )
{
check(Level)
GWarn->BeginSlowTask( LOCTEXT( "ApplyPostEditMove", "Updating all actors in level after move" ), true);
const int32 NumActors = Level->Actors.Num();
// Iterate over all actors in the level and transform them
bMovingLevel = true;
for( int32 ActorIndex=0; ActorIndex < NumActors; ++ActorIndex )
{
GWarn->UpdateProgress( ActorIndex, NumActors );
AActor* Actor = Level->Actors[ActorIndex];
if( Actor )
{
if (!Actor->GetWorld()->IsGameWorld() )
{
Actor->PostEditMove(true);
}
}
}
bMovingLevel = false;
GWarn->EndSlowTask();
}
bool FLevelUtils::IsMovingLevel()
{
return bMovingLevel;
}
bool FLevelUtils::IsApplyingLevelTransform()
{
return bApplyingLevelTransform;
}
#endif // WITH_EDITOR
void FLevelUtils::ApplyLevelTransform(const FLevelUtils::FApplyLevelTransformParams& TransformParams)
{
const bool bTransformActors = !TransformParams.LevelTransform.Equals(FTransform::Identity);
if (bTransformActors)
{
#if WITH_EDITOR
TGuardValue<bool> ApplyingLevelTransformGuard(bApplyingLevelTransform, true);
#endif
// Apply the transform only to the specified actor
if (TransformParams.Actor)
{
if (TransformParams.bSetRelativeTransformDirectly)
{
USceneComponent* RootComponent = TransformParams.Actor->GetRootComponent();
// Don't want to transform children they should stay relative to their parents.
if (RootComponent && RootComponent->GetAttachParent() == nullptr)
{
RootComponent->SetRelativeLocation_Direct(TransformParams.LevelTransform.TransformPosition(RootComponent->GetRelativeLocation()));
RootComponent->SetRelativeRotation_Direct(TransformParams.LevelTransform.TransformRotation(RootComponent->GetRelativeRotation().Quaternion()).Rotator());
RootComponent->SetRelativeScale3D_Direct(TransformParams.LevelTransform.GetScale3D() * RootComponent->GetRelativeScale3D());
}
}
else
{
USceneComponent* RootComponent = TransformParams.Actor->GetRootComponent();
// Don't want to transform children they should stay relative to their parents.
if (RootComponent && RootComponent->GetAttachParent() == nullptr)
{
RootComponent->SetRelativeLocationAndRotation(TransformParams.LevelTransform.TransformPosition(RootComponent->GetRelativeLocation()), TransformParams.LevelTransform.TransformRotation(RootComponent->GetRelativeRotation().Quaternion()));
RootComponent->SetRelativeScale3D(TransformParams.LevelTransform.GetScale3D() * RootComponent->GetRelativeScale3D());
}
}
#if WITH_EDITOR
if (TransformParams.bDoPostEditMove && !TransformParams.Actor->GetWorld()->IsGameWorld())
{
bMovingLevel = true;
TransformParams.Actor->PostEditMove(true);
bMovingLevel = false;
}
#endif
return;
}
// Otherwise do the usual
if (!TransformParams.LevelTransform.GetRotation().IsIdentity())
{
// If there is a rotation applied, then the relative precomputed bounds become invalid.
TransformParams.Level->bTextureStreamingRotationChanged = true;
}
if (TransformParams.bSetRelativeTransformDirectly)
{
// Iterate over all model components to transform BSP geometry accordingly
for (UModelComponent* ModelComponent : TransformParams.Level->ModelComponents)
{
if (ModelComponent)
{
ModelComponent->SetRelativeLocation_Direct(TransformParams.LevelTransform.TransformPosition(ModelComponent->GetRelativeLocation()));
ModelComponent->SetRelativeRotation_Direct(TransformParams.LevelTransform.TransformRotation(ModelComponent->GetRelativeRotation().Quaternion()).Rotator());
ModelComponent->SetRelativeScale3D_Direct(TransformParams.LevelTransform.GetScale3D() * ModelComponent->GetRelativeScale3D());
}
}
// Iterate over all actors in the level and transform them
for (AActor* Actor : TransformParams.Level->Actors)
{
if (Actor)
{
USceneComponent* RootComponent = Actor->GetRootComponent();
// Don't want to transform children they should stay relative to their parents.
if (RootComponent && RootComponent->GetAttachParent() == nullptr)
{
RootComponent->SetRelativeLocation_Direct(TransformParams.LevelTransform.TransformPosition(RootComponent->GetRelativeLocation()));
RootComponent->SetRelativeRotation_Direct(TransformParams.LevelTransform.TransformRotation(RootComponent->GetRelativeRotation().Quaternion()).Rotator());
RootComponent->SetRelativeScale3D_Direct(TransformParams.LevelTransform.GetScale3D() * RootComponent->GetRelativeScale3D());
}
}
}
}
else
{
// Iterate over all model components to transform BSP geometry accordingly
for (UModelComponent* ModelComponent : TransformParams.Level->ModelComponents)
{
if (ModelComponent)
{
ModelComponent->SetRelativeLocationAndRotation(TransformParams.LevelTransform.TransformPosition(ModelComponent->GetRelativeLocation()), TransformParams.LevelTransform.TransformRotation(ModelComponent->GetRelativeRotation().Quaternion()));
ModelComponent->SetRelativeScale3D(TransformParams.LevelTransform.GetScale3D() * ModelComponent->GetRelativeScale3D());
}
}
// Iterate over all actors in the level and transform them
for (AActor* Actor : TransformParams.Level->Actors)
{
if (Actor)
{
USceneComponent* RootComponent = Actor->GetRootComponent();
// Don't want to transform children they should stay relative to their parents.
if (RootComponent && RootComponent->GetAttachParent() == nullptr)
{
RootComponent->SetRelativeLocationAndRotation(TransformParams.LevelTransform.TransformPosition(RootComponent->GetRelativeLocation()), TransformParams.LevelTransform.TransformRotation(RootComponent->GetRelativeRotation().Quaternion()));
RootComponent->SetRelativeScale3D(TransformParams.LevelTransform.GetScale3D() * RootComponent->GetRelativeScale3D());
}
}
}
}
#if WITH_EDITOR
if (TransformParams.bDoPostEditMove)
{
ApplyPostEditMove(TransformParams.Level);
}
#endif // WITH_EDITOR
TransformParams.Level->OnApplyLevelTransform.Broadcast(TransformParams.LevelTransform);
}
}
#undef LOCTEXT_NAMESPACE