Files
UnrealEngineUWP/Engine/Source/Runtime/LevelSequence/Private/LevelSequenceBindingReference.cpp
Andrew Rodham f48639382a Sequencer: Only ever call FixupForPIE for bindings that are being resolved within a World context that has a PIE instance ID
Sometimes binding resolution can be triggered inside a call stack that has a GPlayInEditorID, even if the bindings and resolution context do not. In these situations we were previously erroneously calling FixupForPIE before resolving the soft object path, and ending up finding the PIE actor instead of the one that's actually inside the world context provided. We now explicitly override the GPlayInEditorID for bindings based on the provided context object.

#jira UE-155681
#rb Max.Chen, Ludovic.Chabant
#preflight 62a22c12c12a722b4f065fb1

[CL 20581176 by Andrew Rodham in ue5-main branch]
2022-06-09 14:00:26 -04:00

347 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelSequenceBindingReference.h"
#include "LevelSequenceLegacyObjectReference.h"
#include "UObject/GarbageCollection.h"
#include "UObject/Package.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UObjectGlobals.h"
#include "MovieSceneFwd.h"
#include "Misc/PackageName.h"
#include "Engine/World.h"
#include "Animation/AnimInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/LevelStreamingDynamic.h"
FLevelSequenceBindingReference::FLevelSequenceBindingReference(UObject* InObject, UObject* InContext)
{
check(InContext && InObject);
if (!InContext->IsA<UWorld>() && InObject->IsIn(InContext))
{
ObjectPath = InObject->GetPathName(InContext);
}
else
{
FString FullPath = InObject->GetPathName();
#if WITH_EDITORONLY_DATA
UPackage* ObjectPackage = InObject->GetOutermost();
if (ensure(ObjectPackage))
{
// If this is being set from PIE we need to remove the pie prefix and point to the editor object
if (ObjectPackage->GetPIEInstanceID() != INDEX_NONE)
{
FString PIEPrefix = FString::Printf(PLAYWORLD_PACKAGE_PREFIX TEXT("_%d_"), ObjectPackage->GetPIEInstanceID());
FullPath.ReplaceInline(*PIEPrefix, TEXT(""));
}
}
#endif
ExternalObjectPath = FSoftObjectPath(FullPath);
}
}
UObject* FLevelSequenceBindingReference::Resolve(UObject* InContext, FName StreamedLevelAssetPath) const
{
if (InContext && InContext->IsA<AActor>())
{
if (ExternalObjectPath.IsNull())
{
if (UE::IsSavingPackage(nullptr) || IsGarbageCollecting())
{
return nullptr;
}
return FindObject<UObject>(InContext, *ObjectPath, false);
}
}
else if (InContext && InContext->IsA<ULevel>() && StreamedLevelAssetPath != NAME_None && ExternalObjectPath.GetAssetPathName() == StreamedLevelAssetPath)
{
if (UE::IsSavingPackage(nullptr) || IsGarbageCollecting())
{
return nullptr;
}
// ExternalObjectPath.GetSubPathString() specifies the path from the package (so includes PersistentLevel.) so we must do a FindObject from its outer
return FindObject<UObject>(InContext->GetOuter(), *ExternalObjectPath.GetSubPathString());
}
else
{
FSoftObjectPath TempPath = ExternalObjectPath;
// Soft Object Paths don't follow asset redirectors when attempting to call ResolveObject or TryLoad.
// We want to follow the asset redirector so that maps that have been renamed (from Untitled to their first asset name)
// properly resolve. This fixes Possessable bindings losing their references the first time you save a map.
TempPath.PreSavePath();
#if WITH_EDITORONLY_DATA
// Sequencer is explicit about providing a resolution context for its bindings. We never want to resolve to objects
// with a different PIE instance ID, even if the current callstack is being executed inside a different GPlayInEditorID
// scope. Since ResolveObject will always call FixupForPIE in editor based on GPlayInEditorID, we always override the current
// GPlayInEditorID to be the current PIE instance of the provided context.
const int32 ContextPlayInEditorID = InContext ? InContext->GetOutermost()->GetPIEInstanceID() : INDEX_NONE;
TGuardValue<int32> PIEGuard(GPlayInEditorID, ContextPlayInEditorID);
#endif
return TempPath.ResolveObject();
}
return nullptr;
}
bool FLevelSequenceBindingReference::operator==(const FLevelSequenceBindingReference& Other) const
{
return ExternalObjectPath == Other.ExternalObjectPath && ObjectPath == Other.ObjectPath;
}
void FLevelSequenceBindingReference::PostSerialize(const FArchive& Ar)
{
if (Ar.IsLoading() && !PackageName_DEPRECATED.IsEmpty())
{
// This was saved as two strings, combine into one soft object path so it handles PIE and redirectors properly
FString FullPath = PackageName_DEPRECATED + TEXT(".") + ObjectPath;
ExternalObjectPath.SetPath(FullPath);
ObjectPath.Reset();
PackageName_DEPRECATED.Reset();
}
}
UObject* ResolveByPath(UObject* InContext, const FString& InObjectPath)
{
if (UE::IsSavingPackage(nullptr) || IsGarbageCollecting())
{
return nullptr;
}
if (!InObjectPath.IsEmpty())
{
if (UObject* FoundObject = FindObject<UObject>(InContext, *InObjectPath, false))
{
return FoundObject;
}
#if WITH_EDITOR
UWorld* WorldContext = InContext->GetWorld();
if (WorldContext && WorldContext->IsPlayInEditor())
{
FString PackageRoot, PackagePath, PackageName;
if (FPackageName::SplitLongPackageName(InObjectPath, PackageRoot, PackagePath, PackageName))
{
int32 ObjectDelimiterIdx = INDEX_NONE;
PackageName.FindChar('.', ObjectDelimiterIdx);
const FString SubLevelObjPath = PackageName.Mid(ObjectDelimiterIdx + 1);
for (ULevel* Level : WorldContext->GetLevels())
{
UPackage* Pkg = Level->GetOutermost();
if (UObject* FoundObject = FindObject<UObject>(Pkg, *SubLevelObjPath, false))
{
return FoundObject;
}
}
}
}
#endif
if (UObject* FoundObject = FindFirstObject<UObject>(*InObjectPath, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous))
{
return FoundObject;
}
}
return nullptr;
}
UObject* FLevelSequenceLegacyObjectReference::Resolve(UObject* InContext) const
{
if (ObjectId.IsValid() && InContext != nullptr)
{
int32 PIEInstanceID = InContext->GetOutermost()->GetPIEInstanceID();
FUniqueObjectGuid FixedUpId = PIEInstanceID == -1 ? ObjectId : ObjectId.FixupForPIE(PIEInstanceID);
if (PIEInstanceID != -1 && FixedUpId == ObjectId)
{
UObject* FoundObject = ResolveByPath(InContext, ObjectPath);
if (FoundObject)
{
return FoundObject;
}
UE_LOG(LogMovieScene, Warning, TEXT("Attempted to resolve object (%s) with a PIE instance that has not been fixed up yet. This is probably due to a streamed level not being available yet."), *ObjectPath);
return nullptr;
}
FLazyObjectPtr LazyPtr;
LazyPtr = FixedUpId;
if (UObject* FoundObject = LazyPtr.Get())
{
return FoundObject;
}
}
return ResolveByPath(InContext, ObjectPath);
}
bool FLevelSequenceObjectReferenceMap::Serialize(FArchive& Ar)
{
int32 Num = Map.Num();
Ar << Num;
if (Ar.IsLoading())
{
while(Num-- > 0)
{
FGuid Key;
Ar << Key;
FLevelSequenceLegacyObjectReference Value;
Ar << Value;
Map.Add(Key, Value);
}
}
else if (Ar.IsSaving() || Ar.IsCountingMemory() || Ar.IsObjectReferenceCollector())
{
for (auto& Pair : Map)
{
Ar << Pair.Key;
Ar << Pair.Value;
}
}
return true;
}
bool FLevelSequenceBindingReferences::HasBinding(const FGuid& ObjectId) const
{
return BindingIdToReferences.Contains(ObjectId) || AnimSequenceInstances.Contains(ObjectId);
}
void FLevelSequenceBindingReferences::AddBinding(const FGuid& ObjectId, UObject* InObject, UObject* InContext)
{
if (InObject->IsA<UAnimInstance>())
{
AnimSequenceInstances.Add(ObjectId);
}
else
{
BindingIdToReferences.FindOrAdd(ObjectId).References.Emplace(InObject, InContext);
}
}
void FLevelSequenceBindingReferences::RemoveBinding(const FGuid& ObjectId)
{
BindingIdToReferences.Remove(ObjectId);
AnimSequenceInstances.Remove(ObjectId);
}
void FLevelSequenceBindingReferences::RemoveObjects(const FGuid& ObjectId, const TArray<UObject*>& InObjects, UObject* InContext)
{
FLevelSequenceBindingReferenceArray* ReferenceArray = BindingIdToReferences.Find(ObjectId);
if (!ReferenceArray)
{
return;
}
for (int32 ReferenceIndex = 0; ReferenceIndex < ReferenceArray->References.Num(); )
{
UObject* ResolvedObject = ReferenceArray->References[ReferenceIndex].Resolve(InContext, NAME_None);
if (InObjects.Contains(ResolvedObject))
{
ReferenceArray->References.RemoveAt(ReferenceIndex);
}
else
{
++ReferenceIndex;
}
}
}
void FLevelSequenceBindingReferences::RemoveInvalidObjects(const FGuid& ObjectId, UObject* InContext)
{
FLevelSequenceBindingReferenceArray* ReferenceArray = BindingIdToReferences.Find(ObjectId);
if (!ReferenceArray)
{
return;
}
for (int32 ReferenceIndex = 0; ReferenceIndex < ReferenceArray->References.Num(); )
{
UObject* ResolvedObject = ReferenceArray->References[ReferenceIndex].Resolve(InContext, NAME_None);
if (!IsValid(ResolvedObject))
{
ReferenceArray->References.RemoveAt(ReferenceIndex);
}
else
{
++ReferenceIndex;
}
}
}
void FLevelSequenceBindingReferences::ResolveBinding(const FGuid& ObjectId, UObject* InContext, FName StreamedLevelAssetPath, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
{
if (const FLevelSequenceBindingReferenceArray* ReferenceArray = BindingIdToReferences.Find(ObjectId))
{
for (const FLevelSequenceBindingReference& Reference : ReferenceArray->References)
{
UObject* ResolvedObject = Reference.Resolve(InContext, StreamedLevelAssetPath);
if (ResolvedObject && ResolvedObject->GetWorld())
{
OutObjects.Add(ResolvedObject);
}
}
}
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(InContext))
{
// If the object ID exists in the AnimSequenceInstances set, then this binding relates to an anim instance on a skeletal mesh component
if (SkeletalMeshComponent && AnimSequenceInstances.Contains(ObjectId) && SkeletalMeshComponent->GetAnimInstance())
{
OutObjects.Add(SkeletalMeshComponent->GetAnimInstance());
}
}
}
FGuid FLevelSequenceBindingReferences::FindBindingFromObject(UObject* InObject, UObject* InContext) const
{
FLevelSequenceBindingReference Predicate(InObject, InContext);
for (const TPair<FGuid, FLevelSequenceBindingReferenceArray>& Pair : BindingIdToReferences)
{
if (Pair.Value.References.Contains(Predicate))
{
return Pair.Key;
}
}
return FGuid();
}
void FLevelSequenceBindingReferences::RemoveInvalidBindings(const TSet<FGuid>& ValidBindingIDs)
{
for (auto It = BindingIdToReferences.CreateIterator(); It; ++It)
{
if (!ValidBindingIDs.Contains(It->Key))
{
It.RemoveCurrent();
}
}
}
UObject* FLevelSequenceObjectReferenceMap::ResolveBinding(const FGuid& ObjectId, UObject* InContext) const
{
const FLevelSequenceLegacyObjectReference* Reference = Map.Find(ObjectId);
UObject* ResolvedObject = Reference ? Reference->Resolve(InContext) : nullptr;
if (ResolvedObject != nullptr)
{
// if the resolved object does not have a valid world (e.g. world is being torn down), dont resolve
return ResolvedObject->GetWorld() != nullptr ? ResolvedObject : nullptr;
}
return nullptr;
}