Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Private/MovieSceneSequence.cpp
2025-05-18 13:04:45 +08:00

435 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MovieSceneSequence.h"
#include "Evaluation/MovieSceneEvaluationCustomVersion.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "MovieScene.h"
#include "MovieSceneBindingReferences.h"
#include "UObject/EditorObjectVersion.h"
#include "UObject/ReleaseObjectVersion.h"
#include "UObject/ObjectSaveContext.h"
#include "Tracks/MovieSceneSubTrack.h"
#include "Sections/MovieSceneSubSection.h"
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
#include "Interfaces/ITargetPlatform.h"
#include "EntitySystem/MovieSceneEntityIDs.h"
#include "EntitySystem/MovieSceneEntityManager.h"
#include "EntitySystem/IMovieSceneEntityProvider.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "UniversalObjectLocator.h"
#include "Bindings/MovieSceneSpawnableBinding.h"
#include "MovieSceneCommonHelpers.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneSequence)
UMovieSceneSequence::UMovieSceneSequence(const FObjectInitializer& Init)
: Super(Init)
{
bParentContextsAreSignificant = false;
bPlayableDirectly = true;
SequenceFlags = EMovieSceneSequenceFlags::None;
CompiledData = nullptr;
// Ensure that the precompiled data is set up when constructing the CDO. This guarantees that we do not try and create it for the first time when collecting garbage
if (HasAnyFlags(RF_ClassDefaultObject))
{
UMovieSceneCompiledDataManager::GetPrecompiledData();
#if WITH_EDITOR
UMovieSceneCompiledDataManager::GetPrecompiledData(EMovieSceneServerClientMask::Client);
UMovieSceneCompiledDataManager::GetPrecompiledData(EMovieSceneServerClientMask::Server);
#endif
}
}
bool UMovieSceneSequence::MakeLocatorForObject(UObject* Object, UObject* Context, FUniversalObjectLocator& OutLocator) const
{
if (CanPossessObject(*Object, Context))
{
OutLocator.Reset(Object, Context);
return true;
}
return false;
}
const FMovieSceneBindingReferences* UMovieSceneSequence::GetBindingReferences() const
{
return nullptr;
}
FMovieSceneBindingReferences* UMovieSceneSequence::GetBindingReferences()
{
const FMovieSceneBindingReferences* Result = const_cast<const UMovieSceneSequence*>(this)->GetBindingReferences();
return const_cast<FMovieSceneBindingReferences*>(Result);
}
void UMovieSceneSequence::LocateBoundObjects(const FGuid& ObjectId, const UE::UniversalObjectLocator::FResolveParams& ResolveParams, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
{
LocateBoundObjects(ObjectId, ResolveParams, nullptr, OutObjects);
}
void UMovieSceneSequence::LocateBoundObjects(const FGuid& ObjectId, const UE::UniversalObjectLocator::FResolveParams& ResolveParams, TSharedPtr<const FSharedPlaybackState> SharedPlaybackState, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
{
const FMovieSceneBindingReferences* Refs = GetBindingReferences();
if (Refs)
{
Refs->ResolveBinding(ObjectId, ResolveParams, OutObjects);
}
else
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
LocateBoundObjects(ObjectId, const_cast<UObject*>(ResolveParams.Context), OutObjects);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
}
FGuid UMovieSceneSequence::FindBindingFromObject(UObject* InObject, UObject* Context) const
{
if (InObject && Context)
{
TSharedRef<const UE::MovieScene::FSharedPlaybackState> SharedPlaybackState = MovieSceneHelpers::CreateTransientSharedPlaybackState(Context, const_cast<UMovieSceneSequence*>(this));
return FindBindingFromObject(InObject, SharedPlaybackState);
}
return FGuid();
}
void UMovieSceneSequence::PostLoad()
{
UMovieSceneCompiledDataManager* PrecompiledData = UMovieSceneCompiledDataManager::GetPrecompiledData();
#if WITH_EDITORONLY_DATA
// Wipe compiled data on editor load to ensure we don't try and iteratively compile previously saved content. In a cooked game, this will contain our up-to-date compiled template.
PrecompiledData->Reset(this);
#endif
if (!HasAnyFlags(RF_ClassDefaultObject))
{
PrecompiledData->LoadCompiledData(this);
#if !WITH_EDITOR
// Don't need this any more - allow it to be GC'd so it doesn't take up memory
CompiledData = nullptr;
#else
// Wipe out in -game as well
if (!GIsEditor)
{
CompiledData = nullptr;
}
#endif
}
#if DO_CHECK
if (FPlatformProperties::RequiresCookedData() && !EnumHasAnyFlags(SequenceFlags, EMovieSceneSequenceFlags::Volatile) && !HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
ensureAlwaysMsgf(PrecompiledData->FindDataID(this).IsValid(), TEXT("No precompiled movie scene data is present for sequence '%s'. This should have been generated and saved during cook."), *GetName());
}
#endif
Super::PostLoad();
}
void UMovieSceneSequence::BeginDestroy()
{
Super::BeginDestroy();
if (!GExitPurge && !HasAnyFlags(RF_ClassDefaultObject))
{
UMovieSceneCompiledDataManager::ReportSequenceDestroyed(this);
}
}
void UMovieSceneSequence::PostDuplicate(bool bDuplicateForPIE)
{
if (bDuplicateForPIE)
{
UMovieSceneCompiledDataManager::GetPrecompiledData()->Compile(this);
}
Super::PostDuplicate(bDuplicateForPIE);
}
EMovieSceneServerClientMask UMovieSceneSequence::OverrideNetworkMask(EMovieSceneServerClientMask InDefaultMask) const
{
return InDefaultMask;
}
void UMovieSceneSequence::PreSave(FObjectPreSaveContext ObjectSaveContext)
{
#if WITH_EDITOR
if (!HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
const ITargetPlatform* TargetPlatform = ObjectSaveContext.GetTargetPlatform();
if (TargetPlatform && TargetPlatform->RequiresCookedData())
{
EMovieSceneServerClientMask NetworkMask = EMovieSceneServerClientMask::All;
if (TargetPlatform->IsClientOnly())
{
NetworkMask = EMovieSceneServerClientMask::Client;
}
else if (!TargetPlatform->AllowAudioVisualData())
{
NetworkMask = EMovieSceneServerClientMask::Server;
}
NetworkMask = OverrideNetworkMask(NetworkMask);
if (ObjectSaveContext.IsCooking())
{
OptimizeForCook();
}
UMovieSceneCompiledDataManager::GetPrecompiledData(NetworkMask)->CopyCompiledData(this);
}
else if (CompiledData)
{
// Don't save template data unless we're cooking
CompiledData->Reset();
}
}
#endif
Super::PreSave(ObjectSaveContext);
}
void UMovieSceneSequence::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(FMovieSceneEvaluationCustomVersion::GUID);
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
Ar.UsingCustomVersion(FReleaseObjectVersion::GUID);
Super::Serialize(Ar);
}
#if WITH_EDITOR
void UMovieSceneSequence::OptimizeForCook()
{
// Suppress any change to signature GUIDs, because that could cause cooking indeterminism.
UE::MovieScene::FScopedSignedObjectModifySuppress SignatureChangeSupression(true);
UMovieScene* MovieScene = GetMovieScene();
if (!MovieScene)
{
return;
}
// Go through the root tracks.
for (int32 TrackIndex = 0; TrackIndex < MovieScene->GetTracks().Num(); )
{
UMovieSceneTrack* Track = MovieScene->GetTracks()[TrackIndex];
if (Track && Track->GetCookOptimizationFlags() == ECookOptimizationFlags::RemoveTrack)
{
Track->RemoveForCook();
MovieScene->RemoveTrack(*Track);
UE_LOG(LogMovieScene, Display, TEXT("Removing muted track: %s from: %s"), *Track->GetDisplayName().ToString(), *GetPathName());
continue;
}
++TrackIndex;
}
// Go through the root tracks again and look at sections.
// If a section points to a sub-sequence, also optimize that sub-sequence. We might end up optimizing
// some of these sub-sequences multiple times, if they're used in more than one place, but any
// subsequent times should not do anything.
for (int32 TrackIndex = 0; TrackIndex < MovieScene->GetTracks().Num(); ++TrackIndex)
{
UMovieSceneTrack* Track = MovieScene->GetTracks()[TrackIndex];
if (Track)
{
for (int32 SectionIndex = 0; SectionIndex < Track->GetAllSections().Num(); )
{
UMovieSceneSection* Section = Track->GetAllSections()[SectionIndex];
if (Section && Section->GetCookOptimizationFlags() == ECookOptimizationFlags::RemoveSection)
{
Section->RemoveForCook();
Track->RemoveSection(*Section);
UE_LOG(LogMovieScene, Display, TEXT("Removing muted section: %s from: %s"), *Section->GetPathName(), *Track->GetDisplayName().ToString());
continue;
}
if (UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Section))
{
if (UMovieSceneSequence* SubSequence = SubSection->GetSequence())
{
SubSequence->OptimizeForCook();
}
}
++SectionIndex;
}
}
}
// Go through object bindings.
for (int32 ObjectBindingIndex = 0; ObjectBindingIndex < MovieScene->GetBindings().Num(); )
{
bool bRemoveObject = false;
// First, see if we need to remove the object.
for (int32 TrackIndex = 0; TrackIndex < MovieScene->GetBindings()[ObjectBindingIndex].GetTracks().Num(); ++TrackIndex)
{
UMovieSceneTrack* Track = MovieScene->GetBindings()[ObjectBindingIndex].GetTracks()[TrackIndex];
if (Track && Track->GetCookOptimizationFlags() == ECookOptimizationFlags::RemoveObject)
{
bRemoveObject = true;
break;
}
}
// Then, remove any appropriate tracks, or all tracks if we decided to remove the object altogether.
for (int32 TrackIndex = 0; TrackIndex < MovieScene->GetBindings()[ObjectBindingIndex].GetTracks().Num(); )
{
UMovieSceneTrack* Track = MovieScene->GetBindings()[ObjectBindingIndex].GetTracks()[TrackIndex];
if (Track && (Track->GetCookOptimizationFlags() == ECookOptimizationFlags::RemoveTrack || bRemoveObject))
{
Track->RemoveForCook();
MovieScene->RemoveTrack(*Track);
UE_LOG(LogMovieScene, Display, TEXT("Removing muted track: %s from: %s"), *Track->GetDisplayName().ToString(), *GetPathName());
continue;
}
++TrackIndex;
}
// Go through the tracks again and look at sections.
// Once again, we recurse into sub-sequences if needed (see previous comment).
for (int32 TrackIndex = 0; TrackIndex < MovieScene->GetBindings()[ObjectBindingIndex].GetTracks().Num(); ++TrackIndex)
{
UMovieSceneTrack* Track = MovieScene->GetBindings()[ObjectBindingIndex].GetTracks()[TrackIndex];
if (Track)
{
for (int32 SectionIndex = 0; SectionIndex < Track->GetAllSections().Num(); )
{
UMovieSceneSection* Section = Track->GetAllSections()[SectionIndex];
if (Section && (Section->GetCookOptimizationFlags() == ECookOptimizationFlags::RemoveSection || bRemoveObject))
{
Section->RemoveForCook();
Track->RemoveSection(*Section);
UE_LOG(LogMovieScene, Display, TEXT("Removing muted section: %s from: %s"), *Section->GetPathName(), *Track->GetDisplayName().ToString());
continue;
}
if (UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Section))
{
if (UMovieSceneSequence* SubSequence = SubSection->GetSequence())
{
SubSequence->OptimizeForCook();
}
}
++SectionIndex;
}
}
}
if (bRemoveObject)
{
UE_LOG(LogMovieScene, Display, TEXT("Removing muted object: %s from: %s"), *MovieScene->GetBindings()[ObjectBindingIndex].GetName(), *GetPathName());
FGuid GuidToRemove = MovieScene->GetBindings()[ObjectBindingIndex].GetObjectGuid();
MovieScene->RemoveSpawnable(GuidToRemove);
MovieScene->RemovePossessable(GuidToRemove);
}
else
{
++ObjectBindingIndex;
}
}
}
#endif
UMovieSceneCompiledData* UMovieSceneSequence::GetCompiledData() const
{
return CompiledData;
}
UMovieSceneCompiledData* UMovieSceneSequence::GetOrCreateCompiledData()
{
if (!CompiledData)
{
CompiledData = FindObject<UMovieSceneCompiledData>(this, TEXT("CompiledData"));
if (CompiledData)
{
CompiledData->Reset();
}
else
{
CompiledData = NewObject<UMovieSceneCompiledData>(this, "CompiledData");
}
}
return CompiledData;
}
FGuid UMovieSceneSequence::FindPossessableObjectId(UObject& Object, UObject* Context) const
{
using namespace UE::MovieScene;
UMovieSceneSequence* ThisSequence = const_cast<UMovieSceneSequence*>(this);
TSharedRef<UE::MovieScene::FSharedPlaybackState> TransientPlaybackState = MovieSceneHelpers::CreateTransientSharedPlaybackState(Context, ThisSequence);
if (FMovieSceneEvaluationState* EvaluationState = TransientPlaybackState->FindCapability<FMovieSceneEvaluationState>())
{
FGuid ExistingID = EvaluationState->FindObjectId(Object, MovieSceneSequenceID::Root, TransientPlaybackState);
return ExistingID;
}
return FGuid();
}
FMovieSceneObjectBindingID UMovieSceneSequence::FindBindingByTag(FName InBindingName) const
{
if (const TArray<FMovieSceneObjectBindingID>& Bindings = FindBindingsByTag(InBindingName); Bindings.Num() > 0)
{
return Bindings[0];
}
FMessageLog("PIE")
.Warning(NSLOCTEXT("UMovieSceneSequence", "FindNamedBinding_Warning", "Attempted to find a named binding that did not exist"))
->AddToken(FUObjectToken::Create(this));
return FMovieSceneObjectBindingID();
}
const TArray<FMovieSceneObjectBindingID>& UMovieSceneSequence::FindBindingsByTag(FName InBindingName) const
{
const FMovieSceneObjectBindingIDs* BindingIDs = GetMovieScene()->AllTaggedBindings().Find(InBindingName);
if (BindingIDs)
{
return BindingIDs->IDs;
}
static TArray<FMovieSceneObjectBindingID> EmptyBindings;
return EmptyBindings;
}
FMovieSceneTimecodeSource UMovieSceneSequence::GetEarliestTimecodeSource() const
{
const UMovieScene* MovieScene = GetMovieScene();
if (!MovieScene)
{
return FMovieSceneTimecodeSource();
}
return MovieScene->GetEarliestTimecodeSource();
}
UObject* UMovieSceneSequence::CreateDirectorInstance(IMovieScenePlayer& Player, FMovieSceneSequenceID SequenceID)
{
return CreateDirectorInstance(Player.GetSharedPlaybackState(), SequenceID);
}
#if WITH_EDITOR
ETrackSupport UMovieSceneSequence::IsTrackSupported(TSubclassOf<class UMovieSceneTrack> InTrackClass) const
{
if (!UMovieScene::IsTrackClassAllowed(InTrackClass))
{
return ETrackSupport::NotSupported;
}
return IsTrackSupportedImpl(InTrackClass);
}
bool UMovieSceneSequence::IsFilterSupported(const FString& InFilterName) const
{
return IsFilterSupportedImpl(InFilterName);
}
#endif