Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Private/Compilation/MovieSceneCompiledDataManager.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1967 lines
70 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "Compilation/IMovieSceneTemplateGenerator.h"
#include "Compilation/IMovieSceneTrackTemplateProducer.h"
#include "Compilation/IMovieSceneDeterminismSource.h"
#include "EntitySystem/IMovieSceneEntityProvider.h"
#include "Evaluation/MovieSceneEvaluationCustomVersion.h"
#include "Evaluation/MovieSceneRootOverridePath.h"
#include "MovieScene.h"
#include "MovieSceneSequence.h"
#include "Sections/MovieSceneSubSection.h"
#include "Tracks/MovieSceneSubTrack.h"
#include "Decorations/IMovieSceneDecoration.h"
#include "Decorations/MovieSceneTimeWarpDecoration.h"
#include "IMovieSceneModule.h"
#include "MovieSceneTimeHelpers.h"
#include "MovieSceneTransformTypes.h"
#include "Algo/Sort.h"
#include "Algo/Unique.h"
#include "Containers/SortedMap.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/Package.h"
#include "UObject/PackageReload.h"
#include "MovieSceneCommonHelpers.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneCompiledDataManager)
FString GMovieSceneCompilerVersion = TEXT("7D4B98092FAC4A6B964ECF72D8279EF8");
FAutoConsoleVariableRef CVarMovieSceneCompilerVersion(
TEXT("Sequencer.CompilerVersion"),
GMovieSceneCompilerVersion,
TEXT("Defines a global identifer for moviescene compiler logic.\n"),
ECVF_Default
);
TAutoConsoleVariable<bool> CVarAddKeepStateDeterminismFences(
TEXT("Sequencer.AddKeepStateDeterminismFences"),
true,
TEXT("Whether the Sequencer compiler should auto-add determinism fences for the last frame of KeepState sections. "
"This ensures that the last possible value of the section is consistently evaluated regardless of framerate, "
"at the cost of an extra evaluation on frames that cross over KeepState sections' end time.\n"),
ECVF_Default);
TSet<UMovieSceneCompiledDataManager*> UMovieSceneCompiledDataManager::ActiveManagers;
IMovieSceneModule& GetMovieSceneModule()
{
static TWeakPtr<IMovieSceneModule> WeakMovieSceneModule;
TSharedPtr<IMovieSceneModule> Shared = WeakMovieSceneModule.Pin();
if (!Shared.IsValid())
{
WeakMovieSceneModule = IMovieSceneModule::Get().GetWeakPtr();
Shared = WeakMovieSceneModule.Pin();
}
check(Shared.IsValid());
return *Shared;
}
struct FMovieSceneCompileDataManagerGenerator : public IMovieSceneTemplateGenerator
{
FMovieSceneCompileDataManagerGenerator(UMovieSceneCompiledDataManager* InCompiledDataManager)
{
CompiledDataManager = InCompiledDataManager;
Entry = nullptr;
Template = nullptr;
}
void Reset(FMovieSceneCompiledDataEntry* InEntry)
{
check(InEntry);
Entry = InEntry;
Template = CompiledDataManager->TrackTemplates.Find(Entry->DataID.Value);
}
virtual void AddOwnedTrack(FMovieSceneEvaluationTrack&& InTrackTemplate, const UMovieSceneTrack& SourceTrack) override
{
check(Entry);
if (!Template)
{
Template = &CompiledDataManager->TrackTemplates.FindOrAdd(Entry->DataID.Value);
}
Template->AddTrack(SourceTrack.GetSignature(), MoveTemp(InTrackTemplate));
}
private:
UMovieSceneCompiledDataManager* CompiledDataManager;
FMovieSceneCompiledDataEntry* Entry;
FMovieSceneEvaluationTemplate* Template;
};
struct FCompileOnTheFlyData
{
/** Primary sort - group */
uint16 GroupEvaluationPriority;
/** Secondary sort - Hierarchical bias */
int16 HierarchicalBias;
/** Tertiary sort - Eval priority */
int16 EvaluationPriority;
/** Quaternary sort - Child priority */
int16 ChildPriority;
/** */
FName EvaluationGroup;
/** Whether the track requires initialization or not */
bool bRequiresInit;
bool bPriorityTearDown;
FMovieSceneEvaluationFieldTrackPtr Track;
FMovieSceneFieldEntry_ChildTemplate Child;
};
/** Gathered data for a given time or range */
struct FMovieSceneGatheredCompilerData
{
/** Tree of tracks to evaluate */
TMovieSceneEvaluationTree<FCompileOnTheFlyData> TrackTemplates;
/** Tree of active sequences */
TMovieSceneEvaluationTree<FMovieSceneSequenceID> Sequences;
FMovieSceneEntityComponentField* EntityField = nullptr;
FMovieSceneDeterminismData DeterminismData;
EMovieSceneSequenceFlags InheritedFlags = EMovieSceneSequenceFlags::None;
EMovieSceneSequenceCompilerMask AccumulatedMask = EMovieSceneSequenceCompilerMask::None;
};
/** Parameter structure used for gathering entities for a given time or range */
struct FGatherParameters
{
FGatherParameters()
: SequenceID(MovieSceneSequenceID::Root)
, RootClampRange(TRange<FFrameNumber>::All())
, LocalClampRange(RootClampRange)
, Flags(ESectionEvaluationFlags::None)
, HierarchicalBias(0)
, AccumulatedFlags(EMovieSceneSubSectionFlags::None)
{}
FGatherParameters CreateForSubData(const FMovieSceneSubSequenceData& SubData, FMovieSceneSequenceID InSubSequenceID) const
{
using namespace UE::MovieScene;
FGatherParameters SubParams;
SubParams.SequenceID = InSubSequenceID;
SubParams.RootClampRange = this->RootClampRange;
SubParams.LocalClampRange = UE::MovieScene::ConvertToDiscreteRange(SubData.RootToSequenceTransform.ComputeTraversedHull(this->RootClampRange));
SubParams.Flags = this->Flags;
SubParams.RootToSequenceTransform = SubData.RootToSequenceTransform;
#if WITH_EDITORONLY_DATA
SubParams.RootToUnwarpedLocalTransform = SubData.RootToUnwarpedLocalTransform;
SubParams.StartTimeBreadcrumbs = SubData.StartTimeBreadcrumbs;
SubParams.EndTimeBreadcrumbs = SubData.EndTimeBreadcrumbs;
#else
SubParams.StartTimeBreadcrumbs = this->StartTimeBreadcrumbs;
SubParams.EndTimeBreadcrumbs = this->EndTimeBreadcrumbs;
SubParams.RootToSequenceTransform.TransformTime(DiscreteInclusiveLower(SubData.ParentPlayRange.Value), FTransformTimeParams().AppendBreadcrumbs(SubParams.StartTimeBreadcrumbs));
SubParams.RootToSequenceTransform.TransformTime(DiscreteExclusiveUpper(SubData.ParentPlayRange.Value), FTransformTimeParams().AppendBreadcrumbs(SubParams.EndTimeBreadcrumbs));
#endif
SubParams.HierarchicalBias = SubData.HierarchicalBias;
SubParams.AccumulatedFlags = SubData.AccumulatedFlags;
SubParams.NetworkMask = this->NetworkMask;
return SubParams;
}
void SetClampRange(TRange<FFrameNumber> InNewRootClampRange)
{
RootClampRange = InNewRootClampRange;
LocalClampRange = UE::MovieScene::ConvertToDiscreteRange(RootToSequenceTransform.ComputeTraversedHull(InNewRootClampRange));
}
/** Clamp the specified range to the current clamp range (in root space) */
TRange<FFrameNumber> ClampRoot(const TRange<FFrameNumber>& InRootRange) const
{
return TRange<FFrameNumber>::Intersection(RootClampRange, InRootRange);
}
void TransformLocalRange(const TRange<FFrameNumber>& InLocalRange, TFunctionRef<bool(TRange<FFrameTime>)> InVisitor) const
{
using namespace UE::MovieScene;
TRange<FFrameTime> Range = ConvertToFrameTimeRange(InLocalRange);
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToSequenceTransform.Inverse();
// Linear transforms are easy
if (SequenceToRootTransform.IsLinear())
{
FMovieSceneTimeTransform LinearTransform = SequenceToRootTransform.AsLinear();
if (!Range.GetLowerBound().IsOpen())
{
Range.SetLowerBoundValue(Range.GetLowerBoundValue() * LinearTransform);
}
if (!Range.GetUpperBound().IsOpen())
{
Range.SetUpperBoundValue(Range.GetUpperBoundValue() * LinearTransform);
}
// Normalize inside-out ranges due to negative TimeScale
if (Range.GetLowerBound().IsClosed() && Range.GetUpperBound().IsClosed()
&& Range.GetLowerBoundValue() > Range.GetUpperBoundValue())
{
const auto OldLower = Range.GetLowerBound();
const auto OldUpper = Range.GetUpperBound();
Range.SetLowerBound(OldUpper);
Range.SetUpperBound(OldLower);
}
InVisitor(Range);
return;
}
// Warping transforms are a bit harder
// First off, intersect with the clamp range
if (Range.GetLowerBound().IsOpen() || Range.GetUpperBound().IsOpen())
{
Range = TRange<FFrameTime>::Intersection(Range, RootToSequenceTransform.ComputeTraversedHull(ConvertToFrameTimeRange(RootClampRange)));
}
// Make the range finite based on clamp ranges if possible
if (Range.GetLowerBound().IsOpen() && !RootClampRange.GetLowerBound().IsOpen())
{
TRangeBound<FFrameNumber> LowerBound = RootClampRange.GetLowerBound();
FFrameTime NewTime = RootToSequenceTransform.TransformTime(LowerBound.GetValue());
if (LowerBound.IsInclusive())
{
Range.SetLowerBound(TRangeBound<FFrameTime>::Inclusive(NewTime));
}
else
{
Range.SetLowerBound(TRangeBound<FFrameTime>::Exclusive(NewTime));
}
}
if (Range.GetUpperBound().IsOpen() && !RootClampRange.GetUpperBound().IsOpen())
{
TRangeBound<FFrameNumber> UpperBound = RootClampRange.GetUpperBound();
FFrameTime NewTime = RootToSequenceTransform.TransformTime(UpperBound.GetValue());
if (UpperBound.IsInclusive())
{
Range.SetUpperBound(TRangeBound<FFrameTime>::Inclusive(NewTime));
}
else
{
Range.SetUpperBound(TRangeBound<FFrameTime>::Exclusive(NewTime));
}
}
// Normalize inside-out ranges due to negative TimeScale
if (Range.GetLowerBound().IsClosed() && Range.GetUpperBound().IsClosed()
&& Range.GetLowerBoundValue() > Range.GetUpperBoundValue())
{
const auto OldLower = Range.GetLowerBound();
const auto OldUpper = Range.GetUpperBound();
Range.SetLowerBound(OldUpper);
Range.SetUpperBound(OldLower);
}
if (Range.GetLowerBound().IsOpen() && Range.GetUpperBound().IsOpen())
{
// If the range is infinite we just have to add it all since there's no way for us to transform it.
InVisitor(Range);
}
else if (!Range.GetLowerBound().IsOpen() && !Range.GetUpperBound().IsOpen())
{
// We have a finite range so transform it as many times as it exists in the root space
SequenceToRootTransform.TransformFiniteRangeWithinRange(Range, InVisitor, StartTimeBreadcrumbs, EndTimeBreadcrumbs);
}
else if (Range.GetLowerBound().IsOpen())
{
// Open lower bound so just transform the the upper bound once and compile that
TOptional<FFrameTime> Time = SequenceToRootTransform.TryTransformTime(Range.GetUpperBoundValue(), EndTimeBreadcrumbs);
if (Time)
{
Range.SetUpperBoundValue(Time->FloorToFrame());
InVisitor(Range);
}
}
else if (Range.GetUpperBound().IsOpen())
{
// Open upper bound so just transform the the lower bound once and compile that
TOptional<FFrameTime> Time = SequenceToRootTransform.TryTransformTime(Range.GetLowerBoundValue(), StartTimeBreadcrumbs);
if (Time)
{
Range.SetLowerBoundValue(Time->FloorToFrame());
InVisitor(Range);
}
}
}
/** The ID of the sequence being compiled */
FMovieSceneSequenceID SequenceID;
/** A range to clamp compilation to in the root's time-space */
TRange<FFrameNumber> RootClampRange;
/** A range to clamp compilation to in the current sequence's time-space */
TRange<FFrameNumber> LocalClampRange;
/** Evaluation flags for the current sequence */
ESectionEvaluationFlags Flags;
/** Transform from the root time-space to the current sequence's time-space */
FMovieSceneSequenceTransform RootToSequenceTransform;
#if WITH_EDITORONLY_DATA
/** The transform from root space to this sub-sequence's unwarped local space. */
FMovieSceneSequenceTransform RootToUnwarpedLocalTransform;
#endif
FMovieSceneTransformBreadcrumbs StartTimeBreadcrumbs;
FMovieSceneTransformBreadcrumbs EndTimeBreadcrumbs;
/** Current accumulated hierarchical bias */
int16 HierarchicalBias;
/** Current accumulated sub-section flags */
EMovieSceneSubSectionFlags AccumulatedFlags;
EMovieSceneServerClientMask NetworkMask;
};
/** Parameter structure used for gathering entities for a given time or range */
struct FTrackGatherParameters : FGatherParameters
{
FTrackGatherParameters(UMovieSceneCompiledDataManager* InCompiledDataManager)
: TemplateGenerator(InCompiledDataManager)
{}
FTrackGatherParameters CreateForSubData(const FMovieSceneSubSequenceData& SubData, FMovieSceneSequenceID InSubSequenceID) const
{
FTrackGatherParameters SubParams;
static_cast<FGatherParameters&>(SubParams) = FGatherParameters::CreateForSubData(SubData, InSubSequenceID);
SubParams.TemplateGenerator = this->TemplateGenerator;
return SubParams;
}
/** Store from which to retrieve templates */
mutable FMovieSceneCompileDataManagerGenerator TemplateGenerator;
private:
FTrackGatherParameters()
: TemplateGenerator(nullptr)
{}
};
bool SortPredicate(const FCompileOnTheFlyData& A, const FCompileOnTheFlyData& B)
{
if (A.GroupEvaluationPriority != B.GroupEvaluationPriority)
{
return A.GroupEvaluationPriority > B.GroupEvaluationPriority;
}
else if (A.HierarchicalBias != B.HierarchicalBias)
{
return A.HierarchicalBias < B.HierarchicalBias;
}
else if (A.EvaluationPriority != B.EvaluationPriority)
{
return A.EvaluationPriority > B.EvaluationPriority;
}
else
{
return A.ChildPriority > B.ChildPriority;
}
}
void AddPtrsToGroup(
FMovieSceneEvaluationGroup* OutGroup,
TArray<FMovieSceneFieldEntry_EvaluationTrack>& InitTrackLUT,
TArray<FMovieSceneFieldEntry_ChildTemplate>& InitSectionLUT,
TArray<FMovieSceneFieldEntry_EvaluationTrack>& EvalTrackLUT,
TArray<FMovieSceneFieldEntry_ChildTemplate>& EvalSectionLUT
)
{
if (!InitTrackLUT.Num() && !EvalTrackLUT.Num())
{
return;
}
FMovieSceneEvaluationGroupLUTIndex Index;
Index.NumInitPtrs = InitTrackLUT.Num();
Index.NumEvalPtrs = EvalTrackLUT.Num();
OutGroup->LUTIndices.Add(Index);
OutGroup->TrackLUT.Append(InitTrackLUT);
OutGroup->TrackLUT.Append(EvalTrackLUT);
OutGroup->SectionLUT.Append(InitSectionLUT);
OutGroup->SectionLUT.Append(EvalSectionLUT);
InitTrackLUT.Reset();
InitSectionLUT.Reset();
EvalTrackLUT.Reset();
EvalSectionLUT.Reset();
}
FMovieSceneCompiledDataEntry::FMovieSceneCompiledDataEntry()
: AccumulatedFlags(EMovieSceneSequenceFlags::None)
, AccumulatedMask(EMovieSceneSequenceCompilerMask::None)
{}
UMovieSceneSequence* FMovieSceneCompiledDataEntry::GetSequence() const
{
return CastChecked<UMovieSceneSequence>(SequenceKey.ResolveObjectPtr(), ECastCheckedType::NullAllowed);
}
UMovieSceneCompiledData::UMovieSceneCompiledData()
{
AccumulatedMask = EMovieSceneSequenceCompilerMask::None;
AllocatedMask = EMovieSceneSequenceCompilerMask::None;
AccumulatedFlags = EMovieSceneSequenceFlags::None;
}
void UMovieSceneCompiledData::Reset()
{
EvaluationTemplate = FMovieSceneEvaluationTemplate();
Hierarchy = FMovieSceneSequenceHierarchy();
EntityComponentField = FMovieSceneEntityComponentField();
TrackTemplateField = FMovieSceneEvaluationField();
DeterminismFences.Reset();
CompiledSignature.Invalidate();
CompilerVersion.Invalidate();
AccumulatedMask = EMovieSceneSequenceCompilerMask::None;
AllocatedMask = EMovieSceneSequenceCompilerMask::None;
AccumulatedFlags = EMovieSceneSequenceFlags::None;
}
#if WITH_EDITORONLY_DATA
void UMovieSceneCompiledData::AppendToClassSchema(FAppendToClassSchemaContext& Context)
{
Super::AppendToClassSchema(Context);
// Specify the compiler version to the iterative cooker. Any changes to the schema of
// compiled data should update the version to ensure that compiled data is invalidated
// for the purposes of iterative cooking.
FGuid ParsedCompilerVersion;
if (FGuid::Parse(GMovieSceneCompilerVersion, ParsedCompilerVersion))
{
Context.Update(&ParsedCompilerVersion, sizeof(ParsedCompilerVersion));
}
}
#endif
UMovieSceneCompiledDataManager::UMovieSceneCompiledDataManager()
{
const bool bParsed = FGuid::Parse(GMovieSceneCompilerVersion, CompilerVersion);
ensureMsgf(bParsed, TEXT("Invalid compiler version specified - this will break any persistent compiled data"));
IConsoleManager::Get().RegisterConsoleVariableSink_Handle(FConsoleCommandDelegate::CreateUObject(this, &UMovieSceneCompiledDataManager::ConsoleVariableSink));
ReallocationVersion = 0;
NetworkMask = EMovieSceneServerClientMask::All;
auto OnPackageReloaded = [this](const EPackageReloadPhase InPackageReloadPhase, FPackageReloadedEvent* InPackageReloadedEvent)
{
if (InPackageReloadPhase != EPackageReloadPhase::OnPackageFixup)
{
return;
}
for (const TPair<UObject*, UObject*>& Pair : InPackageReloadedEvent->GetRepointedObjects())
{
UMovieSceneSequence* OldSequence = Cast<UMovieSceneSequence>(Pair.Key);
UMovieSceneSequence* NewSequence = Cast<UMovieSceneSequence>(Pair.Value);
if (OldSequence && NewSequence)
{
FMovieSceneCompiledDataID DataID = this->SequenceToDataIDs.FindRef(OldSequence);
if (DataID.IsValid())
{
// Repoint the data ID for the old sequence to the new sequence
{
FMovieSceneCompiledDataEntry& Entry = CompiledDataEntries[DataID.Value];
this->SequenceToDataIDs.Remove(Entry.SequenceKey);
// Entry is a ref here, so care is taken to ensure we do not allocate CompiledDataEntries while the ref is around
Entry = FMovieSceneCompiledDataEntry();
Entry.SequenceKey = NewSequence;
Entry.DataID = DataID;
this->SequenceToDataIDs.Add(Entry.SequenceKey, DataID);
}
// Destroy all the old compiled data as it is no longer valid
this->Hierarchies.Remove(DataID.Value);
this->TrackTemplates.Remove(DataID.Value);
this->TrackTemplateFields.Remove(DataID.Value);
this->EntityComponentFields.Remove(DataID.Value);
++this->ReallocationVersion;
}
}
}
};
if (!HasAnyFlags(RF_ClassDefaultObject))
{
FCoreUObjectDelegates::OnPackageReloaded.AddWeakLambda(this, OnPackageReloaded);
ActiveManagers.Add(this);
}
}
void UMovieSceneCompiledDataManager::BeginDestroy()
{
ActiveManagers.Remove(this);
Super::BeginDestroy();
}
void UMovieSceneCompiledDataManager::ReportSequenceDestroyed(UMovieSceneSequence* InSequence)
{
if (!GExitPurge)
{
for (UMovieSceneCompiledDataManager* Manager : ActiveManagers)
{
Manager->Reset(InSequence);
}
}
}
void UMovieSceneCompiledDataManager::DestroyAllData()
{
// Eradicate all compiled data
for (int32 Index = 0; Index < CompiledDataEntries.GetMaxIndex(); ++Index)
{
if (CompiledDataEntries.IsAllocated(Index))
{
FMovieSceneCompiledDataEntry& Entry = CompiledDataEntries[Index];
Entry.CompiledSignature = FGuid();
Entry.AccumulatedFlags = EMovieSceneSequenceFlags::None;
Entry.AccumulatedMask = EMovieSceneSequenceCompilerMask::None;
}
}
Hierarchies.Empty();
TrackTemplates.Empty();
TrackTemplateFields.Empty();
EntityComponentFields.Empty();
}
void UMovieSceneCompiledDataManager::ConsoleVariableSink()
{
FGuid NewCompilerVersion;
const bool bParsed = FGuid::Parse(GMovieSceneCompilerVersion, NewCompilerVersion);
ensureMsgf(bParsed, TEXT("Invalid compiler version specific - this will break any persistent compiled data"));
if (CompilerVersion != NewCompilerVersion)
{
DestroyAllData();
}
}
void UMovieSceneCompiledDataManager::CopyCompiledData(UMovieSceneSequence* Sequence)
{
UMovieSceneCompiledData* CompiledData = Sequence->GetOrCreateCompiledData();
CompiledData->Reset();
FMovieSceneCompiledDataID DataID = GetDataID(Sequence);
Compile(DataID, Sequence);
if (const FMovieSceneSequenceHierarchy* Hierarchy = FindHierarchy(DataID))
{
CompiledData->Hierarchy = *Hierarchy;
CompiledData->AllocatedMask |= EMovieSceneSequenceCompilerMask::Hierarchy;
}
if (const FMovieSceneEvaluationTemplate* TrackTemplate = FindTrackTemplate(DataID))
{
CompiledData->EvaluationTemplate = *TrackTemplate;
CompiledData->AllocatedMask |= EMovieSceneSequenceCompilerMask::EvaluationTemplate;
}
if (const FMovieSceneEvaluationField* TrackTemplateField = FindTrackTemplateField(DataID))
{
if (Sequence->IsPlayableDirectly())
{
CompiledData->TrackTemplateField = *TrackTemplateField;
CompiledData->AllocatedMask |= EMovieSceneSequenceCompilerMask::EvaluationTemplateField;
}
}
if (const FMovieSceneEntityComponentField* EntityComponentField = FindEntityComponentField(DataID))
{
CompiledData->EntityComponentField = *EntityComponentField;
CompiledData->AllocatedMask |= EMovieSceneSequenceCompilerMask::EntityComponentField;
}
const FMovieSceneCompiledDataEntry& DataEntry = CompiledDataEntries[DataID.Value];
CompiledData->DeterminismFences = DataEntry.DeterminismFences;
CompiledData->CompiledSignature = Sequence->GetSignature();
CompiledData->CompilerVersion = CompilerVersion;
CompiledData->AccumulatedMask = DataEntry.AccumulatedMask;
CompiledData->AccumulatedFlags = DataEntry.AccumulatedFlags;
CompiledData->CompiledFlags = DataEntry.CompiledFlags;
}
void UMovieSceneCompiledDataManager::LoadCompiledData(UMovieSceneSequence* Sequence)
{
// This can be called during Async Loads
FScopeLock AsyncLoadLock(&AsyncLoadCriticalSection);
UMovieSceneCompiledData* CompiledData = Sequence->GetCompiledData();
if (CompiledData)
{
FMovieSceneCompiledDataID DataID = GetDataID(Sequence);
if (CompiledData->CompilerVersion != CompilerVersion)
{
CompiledDataEntries[DataID.Value].AccumulatedFlags |= EMovieSceneSequenceFlags::Volatile;
return;
}
if (EnumHasAnyFlags(CompiledData->AllocatedMask, EMovieSceneSequenceCompilerMask::Hierarchy))
{
Hierarchies.Add(DataID.Value, MoveTemp(CompiledData->Hierarchy));
}
if (EnumHasAnyFlags(CompiledData->AllocatedMask, EMovieSceneSequenceCompilerMask::EvaluationTemplate))
{
TrackTemplates.Add(DataID.Value, MoveTemp(CompiledData->EvaluationTemplate));
}
if (EnumHasAnyFlags(CompiledData->AllocatedMask, EMovieSceneSequenceCompilerMask::EvaluationTemplateField))
{
TrackTemplateFields.Add(DataID.Value, MoveTemp(CompiledData->TrackTemplateField));
}
if (EnumHasAnyFlags(CompiledData->AllocatedMask, EMovieSceneSequenceCompilerMask::EntityComponentField))
{
EntityComponentFields.Add(DataID.Value, MoveTemp(CompiledData->EntityComponentField));
}
FMovieSceneCompiledDataEntry* EntryPtr = GetEntryPtr(DataID);
EntryPtr->DeterminismFences = MoveTemp(CompiledData->DeterminismFences);
EntryPtr->CompiledSignature = CompiledData->CompiledSignature;
EntryPtr->AccumulatedMask = CompiledData->AccumulatedMask;
EntryPtr->AccumulatedFlags = CompiledData->AccumulatedFlags;
EntryPtr->CompiledFlags = CompiledData->CompiledFlags;
++ReallocationVersion;
}
else
{
Reset(Sequence);
}
}
bool UMovieSceneCompiledDataManager::CanMarkSignedObjectAsChangedDuringCook(UMovieSceneSequence* Sequence) const
{
const FMovieSceneCompiledDataID DataID = FindDataID(Sequence);
if (!DataID.IsValid())
{
// No data ID has been created, so this sequence hasn't been compiled yet.
// We're OK to modify it.
return true;
}
const FMovieSceneCompiledDataEntry* EntryPtr = GetEntryPtr(DataID);
// If the compiled signature is set, we have already compiled the sequence. In that
// case, it's not OK to modify data anymore.
return !EntryPtr->CompiledSignature.IsValid();
}
void UMovieSceneCompiledDataManager::SetEmulatedNetworkMask(EMovieSceneServerClientMask NewMask)
{
DestroyAllData();
NetworkMask = NewMask;
}
void UMovieSceneCompiledDataManager::Reset(UMovieSceneSequence* Sequence)
{
// Care is taken here not to use GetDataID which _creates_ a new data ID if
// one is not available. This ensures that calling Reset() does not create
// new data for sequences that have not yet been encountered
FMovieSceneCompiledDataID DataID = SequenceToDataIDs.FindRef(Sequence);
if (DataID.IsValid())
{
DestroyData(DataID);
SequenceToDataIDs.Remove(Sequence);
}
}
FMovieSceneCompiledDataID UMovieSceneCompiledDataManager::FindDataID(UMovieSceneSequence* Sequence) const
{
return SequenceToDataIDs.FindRef(Sequence);
}
FMovieSceneCompiledDataID UMovieSceneCompiledDataManager::GetDataID(UMovieSceneSequence* Sequence)
{
check(Sequence);
FMovieSceneCompiledDataID ExistingDataID = FindDataID(Sequence);
if (ExistingDataID.IsValid())
{
return ExistingDataID;
}
const int32 Index = CompiledDataEntries.Add(FMovieSceneCompiledDataEntry());
ExistingDataID = FMovieSceneCompiledDataID { Index };
FMovieSceneCompiledDataEntry& NewEntry = CompiledDataEntries[Index];
NewEntry.SequenceKey = Sequence;
NewEntry.DataID = ExistingDataID;
NewEntry.AccumulatedFlags = Sequence->GetFlags();
SequenceToDataIDs.Add(Sequence, ExistingDataID);
return ExistingDataID;
}
FMovieSceneCompiledDataID UMovieSceneCompiledDataManager::GetSubDataID(FMovieSceneCompiledDataID DataID, FMovieSceneSequenceID SubSequenceID)
{
if (SubSequenceID == MovieSceneSequenceID::Root)
{
return DataID;
}
const FMovieSceneSequenceHierarchy* Hierarchy = FindHierarchy(DataID);
if (Hierarchy)
{
const FMovieSceneSubSequenceData* SubData = Hierarchy->FindSubData(SubSequenceID);
UMovieSceneSequence* SubSequence = SubData ? SubData->GetSequence() : nullptr;
if (SubSequence)
{
return GetDataID(SubSequence);
}
}
return FMovieSceneCompiledDataID();
}
#if WITH_EDITOR
UMovieSceneCompiledDataManager* UMovieSceneCompiledDataManager::GetPrecompiledData(EMovieSceneServerClientMask EmulatedMask)
{
ensureMsgf(!GExitPurge, TEXT("Attempting to access precompiled data manager during shutdown - this is undefined behavior since the manager may have already been destroyed, or could be unconstrictible"));
if (EmulatedMask == EMovieSceneServerClientMask::Client)
{
static UMovieSceneCompiledDataManager* GEmulatedClientDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "EmulatedClientDataManager", RF_MarkAsRootSet);
GEmulatedClientDataManager->NetworkMask = EMovieSceneServerClientMask::Client;
return GEmulatedClientDataManager;
}
if (EmulatedMask == EMovieSceneServerClientMask::Server)
{
static UMovieSceneCompiledDataManager* GEmulatedServerDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "EmulatedServerDataManager", RF_MarkAsRootSet);
GEmulatedServerDataManager->NetworkMask = EMovieSceneServerClientMask::Server;
return GEmulatedServerDataManager;
}
static UMovieSceneCompiledDataManager* GPrecompiledDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "PrecompiledDataManager", RF_MarkAsRootSet);
return GPrecompiledDataManager;
}
#else // WITH_EDITOR
UMovieSceneCompiledDataManager* UMovieSceneCompiledDataManager::GetPrecompiledData()
{
ensureMsgf(!GExitPurge, TEXT("Attempting to access precompiled data manager during shutdown - this is undefined behavior since the manager may have already been destroyed, or could be unconstrictible"));
static UMovieSceneCompiledDataManager* GPrecompiledDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "PrecompiledDataManager", RF_MarkAsRootSet);
return GPrecompiledDataManager;
}
#endif // WITH_EDITOR
void UMovieSceneCompiledDataManager::DestroyData(FMovieSceneCompiledDataID DataID)
{
check(DataID.IsValid() && CompiledDataEntries.IsValidIndex(DataID.Value));
Hierarchies.Remove(DataID.Value);
TrackTemplates.Remove(DataID.Value);
TrackTemplateFields.Remove(DataID.Value);
EntityComponentFields.Remove(DataID.Value);
CompiledDataEntries.RemoveAt(DataID.Value);
}
void UMovieSceneCompiledDataManager::DestroyTemplate(FMovieSceneCompiledDataID DataID)
{
check(DataID.IsValid() && CompiledDataEntries.IsValidIndex(DataID.Value));
// Remove the lookup entry for this sequence/network mask combination
const FMovieSceneCompiledDataEntry& Entry = CompiledDataEntries[DataID.Value];
SequenceToDataIDs.Remove(Entry.SequenceKey);
DestroyData(DataID);
}
bool UMovieSceneCompiledDataManager::IsDirty(const FMovieSceneCompiledDataEntry& Entry) const
{
if (!Entry.GetSequence())
{
return false;
}
if (Entry.CompiledSignature != Entry.GetSequence()->GetSignature())
{
return true;
}
if (const FMovieSceneSequenceHierarchy* Hierarchy = FindHierarchy(Entry.DataID))
{
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy->AllSubSequenceData())
{
if (UMovieSceneSequence* SubSequence = Pair.Value.GetSequence())
{
FMovieSceneCompiledDataID SubDataID = FindDataID(SubSequence);
if (!SubDataID.IsValid() || CompiledDataEntries[SubDataID.Value].CompiledSignature != SubSequence->GetSignature())
{
return true;
}
}
else
{
return true;
}
}
}
return false;
}
bool UMovieSceneCompiledDataManager::IsDirty(FMovieSceneCompiledDataID CompiledDataID) const
{
check(CompiledDataID.IsValid() && CompiledDataEntries.IsValidIndex(CompiledDataID.Value));
return IsDirty(CompiledDataEntries[CompiledDataID.Value]);
}
bool UMovieSceneCompiledDataManager::IsDirty(UMovieSceneSequence* Sequence) const
{
FMovieSceneCompiledDataID ExistingDataID = FindDataID(Sequence);
if (ExistingDataID.IsValid())
{
check(CompiledDataEntries.IsValidIndex(ExistingDataID.Value));
FMovieSceneCompiledDataEntry Entry = CompiledDataEntries[ExistingDataID.Value];
return IsDirty(Entry);
}
return true;
}
bool UMovieSceneCompiledDataManager::ValidateEntry(FMovieSceneCompiledDataID DataID, UMovieSceneSequence* Sequence) const
{
if (!ensureMsgf(
CompiledDataEntries.IsValidIndex(DataID.Value),
TEXT("Given DataID %d is not valid! (%d entries in the data manager)"), DataID.Value, CompiledDataEntries.Num()))
{
return false;
}
const FMovieSceneCompiledDataEntry& Entry = CompiledDataEntries[DataID.Value];
UMovieSceneSequence* EntrySequence = Entry.GetSequence();
if (!ensureMsgf(
EntrySequence == Sequence,
TEXT("Unexpected sequence for data ID! Expected '%s', but data manager has '%s'."), *GetNameSafe(Sequence), *GetNameSafe(EntrySequence)))
{
return false;
}
return true;
}
void UMovieSceneCompiledDataManager::Compile(FMovieSceneCompiledDataID DataID)
{
Compile(DataID, NetworkMask);
}
void UMovieSceneCompiledDataManager::Compile(FMovieSceneCompiledDataID DataID, EMovieSceneServerClientMask InNetworkMask)
{
check(DataID.IsValid() && CompiledDataEntries.IsValidIndex(DataID.Value));
UMovieSceneSequence* Sequence = CompiledDataEntries[DataID.Value].GetSequence();
check(Sequence);
Compile(DataID, Sequence, InNetworkMask);
}
FMovieSceneCompiledDataID UMovieSceneCompiledDataManager::Compile(UMovieSceneSequence* Sequence)
{
FMovieSceneCompiledDataID DataID = GetDataID(Sequence);
Compile(DataID, Sequence);
return DataID;
}
void UMovieSceneCompiledDataManager::Compile(FMovieSceneCompiledDataID DataID, UMovieSceneSequence* Sequence)
{
Compile(DataID, Sequence, NetworkMask);
}
void UMovieSceneCompiledDataManager::Compile(FMovieSceneCompiledDataID DataID, UMovieSceneSequence* Sequence, EMovieSceneServerClientMask InNetworkMask)
{
check(DataID.IsValid() && CompiledDataEntries.IsValidIndex(DataID.Value));
FMovieSceneCompiledDataEntry Entry = CompiledDataEntries[DataID.Value];
if (!IsDirty(Entry))
{
return;
}
FMovieSceneGatheredCompilerData GatheredData;
FTrackGatherParameters Params(this);
Entry.DeterminismFences.Empty();
Entry.AccumulatedFlags = Sequence->GetFlags();
Params.TemplateGenerator.Reset(&Entry);
Params.NetworkMask = InNetworkMask;
// Clear list of generated conditions
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (ensure(MovieScene))
{
for (TObjectPtr<UObject> DecorationObject : MovieScene->GetDecorations())
{
if (IMovieSceneDecoration* Decoration = Cast<IMovieSceneDecoration>(DecorationObject))
{
Decoration->OnPreDecorationCompiled();
}
}
MovieScene->ResetGeneratedConditions();
}
// ---------------------------------------------------------------------------------------------------
// Step 1 - Always ensure the hierarchy information is completely up to date first
FMovieSceneSequenceHierarchy NewHierarchy;
const bool bHasHierarchy = CompileHierarchy(Sequence, Params, &NewHierarchy);
// If the network mask of the compiled data manager is 'all', but the sequence has been created with client-only and/or server-only subsections,
// then we mark the sequence volatile as we may need to recompile it at runtime in order to exclude these subsections depending on the net mode at runtime.
if (Params.NetworkMask == EMovieSceneServerClientMask::All && NewHierarchy.GetAccumulatedNetworkMask() != EMovieSceneServerClientMask::All)
{
Entry.AccumulatedFlags |= EMovieSceneSequenceFlags::Volatile;
}
if (IMovieSceneDeterminismSource* DeterminismSource = Cast<IMovieSceneDeterminismSource>(Sequence))
{
DeterminismSource->PopulateDeterminismData(GatheredData.DeterminismData, TRange<FFrameNumber>::All());
}
TSet<FGuid> GatheredSignatures;
{
if (ensure(MovieScene))
{
for (const FMovieSceneMarkedFrame& Mark : MovieScene->GetMarkedFrames())
{
if (Mark.bIsDeterminismFence)
{
GatheredData.DeterminismData.Fences.Emplace(Mark.FrameNumber, Mark.bIsInclusiveTime);
}
}
if (UMovieSceneTrack* Track = MovieScene->GetCameraCutTrack())
{
CompileTrack(&Entry, nullptr, Track, Params, &GatheredSignatures, &GatheredData);
}
for (UMovieSceneTrack* Track : MovieScene->GetTracks())
{
CompileTrack(&Entry, nullptr, Track, Params, &GatheredSignatures, &GatheredData);
}
for (const FMovieSceneBinding& ObjectBinding : ((const UMovieScene*)MovieScene)->GetBindings())
{
for (UMovieSceneTrack* Track : ObjectBinding.GetTracks())
{
CompileTrack(&Entry, &ObjectBinding, Track, Params, &GatheredSignatures, &GatheredData);
}
}
}
}
// ---------------------------------------------------------------------------------------------------
// Step 2 - Gather compilation data
FMovieSceneEntityComponentField ThisSequenceEntityField;
{
GatheredData.EntityField = &ThisSequenceEntityField;
Gather(Entry, Sequence, Params, &GatheredData);
GatheredData.EntityField = nullptr;
}
// ---------------------------------------------------------------------------------------------------
// Step 3 - Assign entity field from data gathered for _this sequence only_
if (ThisSequenceEntityField.IsEmpty())
{
EntityComponentFields.Remove(DataID.Value);
}
else
{
// EntityComponent data is not flattened so we assign that now after the initial gather
EntityComponentFields.FindOrAdd(DataID.Value) = MoveTemp(ThisSequenceEntityField);
GatheredData.AccumulatedMask |= EMovieSceneSequenceCompilerMask::EntityComponentField;
}
// ---------------------------------------------------------------------------------------------------
// Step 4 - If we have a hierarchy, perform a gather for sub sequences
if (bHasHierarchy)
{
CompileSubSequences(NewHierarchy, Params, &GatheredData);
Entry.AccumulatedFlags |= GatheredData.InheritedFlags;
Entry.AccumulatedMask |= GatheredData.AccumulatedMask;
}
// ---------------------------------------------------------------------------------------------------
// Step 5 - Consolidate track template data from gathered data
if (FMovieSceneEvaluationTemplate* TrackTemplate = TrackTemplates.Find(Entry.DataID.Value))
{
TrackTemplate->RemoveStaleData(GatheredSignatures);
}
CompileTrackTemplateField(&Entry, NewHierarchy, &GatheredData);
// ---------------------------------------------------------------------------------------------------
// Step 6 - Reassign or remove the new hierarchy
if (bHasHierarchy)
{
Hierarchies.FindOrAdd(DataID.Value) = MoveTemp(NewHierarchy);
}
else
{
Hierarchies.Remove(DataID.Value);
}
// ---------------------------------------------------------------------------------------------------
// Step 7: Apply the final state to the entry
Entry.CompiledFlags.bParentSequenceRequiresLowerFence = GatheredData.DeterminismData.bParentSequenceRequiresLowerFence;
Entry.CompiledFlags.bParentSequenceRequiresUpperFence = GatheredData.DeterminismData.bParentSequenceRequiresUpperFence;
Entry.CompiledSignature = Sequence->GetSignature();
Entry.AccumulatedMask = GatheredData.AccumulatedMask;
Entry.DeterminismFences = MoveTemp(GatheredData.DeterminismData.Fences);
if (Entry.DeterminismFences.Num())
{
Algo::SortBy(Entry.DeterminismFences, &FMovieSceneDeterminismFence::FrameNumber);
const int32 NewNum = Algo::Unique(Entry.DeterminismFences);
if (NewNum != Entry.DeterminismFences.Num())
{
Entry.DeterminismFences.SetNum(NewNum);
}
}
CompiledDataEntries[DataID.Value] = Entry;
++ReallocationVersion;
for (TObjectPtr<UObject> DecorationObject : Sequence->GetMovieScene()->GetDecorations())
{
if (IMovieSceneDecoration* Decoration = Cast<IMovieSceneDecoration>(DecorationObject))
{
Decoration->OnPostDecorationCompiled();
}
}
#if 0
#if !NO_LOGGING
if (bHasHierarchy)
{
FMovieSceneSequenceHierarchy* HierarchyToLog = Hierarchies.Find(DataID.Value);
if (ensure(HierarchyToLog))
{
UE_LOG(LogMovieScene, Log, TEXT("Newly compiled sequence hierarchy:"));
HierarchyToLog->LogHierarchy();
HierarchyToLog->LogSubSequenceTree();
}
}
else
{
UE_LOG(LogMovieScene, Log, TEXT("No sequence hierarchy"));
}
#endif
#endif
}
void UMovieSceneCompiledDataManager::Gather(const FMovieSceneCompiledDataEntry& Entry, UMovieSceneSequence* Sequence, const FTrackGatherParameters& Params, FMovieSceneGatheredCompilerData* OutCompilerData) const
{
const FMovieSceneEvaluationTemplate* TrackTemplate = FindTrackTemplate(Entry.DataID);
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (ensure(MovieScene))
{
// Allow decorations on the movie scene to define entities in the field
if (OutCompilerData->EntityField)
{
FMovieSceneEntityComponentFieldBuilder FieldBuilder(OutCompilerData->EntityField);
for (TObjectPtr<UObject> DecorationObject : MovieScene->GetDecorations())
{
if (IMovieSceneEntityProvider* Provider = Cast<IMovieSceneEntityProvider>(DecorationObject))
{
FMovieSceneEvaluationFieldEntityMetaData MetaData;
Provider->PopulateEvaluationField(TRange<FFrameNumber>::All(), MetaData, &FieldBuilder);
}
}
}
if (UMovieSceneTrack* Track = MovieScene->GetCameraCutTrack())
{
GatherTrack(nullptr, Track, Params, TrackTemplate, OutCompilerData);
}
for (UMovieSceneTrack* Track : MovieScene->GetTracks())
{
GatherTrack(nullptr, Track, Params, TrackTemplate, OutCompilerData);
}
for (const FMovieSceneBinding& ObjectBinding : ((const UMovieScene*)MovieScene)->GetBindings())
{
for (UMovieSceneTrack* Track : ObjectBinding.GetTracks())
{
GatherTrack(&ObjectBinding, Track, Params, TrackTemplate, OutCompilerData);
}
}
}
}
void UMovieSceneCompiledDataManager::CompileSubSequences(const FMovieSceneSequenceHierarchy& Hierarchy, const FTrackGatherParameters& Params, FMovieSceneGatheredCompilerData* OutCompilerData)
{
using namespace UE::MovieScene;
OutCompilerData->AccumulatedMask |= EMovieSceneSequenceCompilerMask::Hierarchy;
// Ensure all sub sequences are compiled
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy.AllSubSequenceData())
{
if (UMovieSceneSequence* SubSequence = Pair.Value.GetSequence())
{
Compile(SubSequence);
}
}
const TMovieSceneEvaluationTree<FMovieSceneSubSequenceTreeEntry>& SubSequenceTree = Hierarchy.GetTree();
// When adding determinism fences for sub sequences, we track the iteration index for each sequence ID so that
// we only add a fence when the sub sequence truly ends or begins, not for every segmentation of the sub sequence tree
struct FSubSequenceItMetaData
{
int32 LastIterIndex = INDEX_NONE;
TOptional<FFrameNumber> TrailingFence;
};
TSortedMap<FMovieSceneSequenceID, FSubSequenceItMetaData> ItMetaData;
// Start iterating the field from the lower bound of the compile range
FMovieSceneEvaluationTreeRangeIterator SubSequenceIt = SubSequenceTree.IterateFromLowerBound(Params.RootClampRange.GetLowerBound());
for ( int32 ItIndex = 0; SubSequenceIt && SubSequenceIt.Range().Overlaps(Params.RootClampRange); ++SubSequenceIt, ++ItIndex)
{
// Iterate all sub sequences in the current range
for (const FMovieSceneSubSequenceTreeEntry& SubSequenceEntry : SubSequenceTree.GetAllData(SubSequenceIt.Node()))
{
FMovieSceneSequenceID SubSequenceID = SubSequenceEntry.SequenceID;
const FMovieSceneSubSequenceData* SubData = Hierarchy.FindSubData(SubSequenceID);
checkf(SubData, TEXT("Sub data could not be found for a sequence that exists in the sub sequence tree - this indicates an error while populating the sub sequence hierarchy tree."));
UMovieSceneSequence* SubSequence = SubData->GetSequence();
if (SubSequence)
{
FTrackGatherParameters SubSectionGatherParams = Params.CreateForSubData(*SubData, SubSequenceID);
SubSectionGatherParams.Flags |= SubSequenceEntry.Flags;
SubSectionGatherParams.SetClampRange(SubSequenceIt.Range());
// Access the sub entry data after compilation
FMovieSceneCompiledDataID SubDataID = GetDataID(SubSequence);
check(SubDataID.IsValid());
// Gather track template data for the sub sequence
FMovieSceneCompiledDataEntry SubEntry = CompiledDataEntries[SubDataID.Value];
if (TrackTemplates.Contains(SubDataID.Value))
{
Gather(SubEntry, SubSequence, SubSectionGatherParams, OutCompilerData);
}
// Inherit flags from sub sequences (if a sub sequence is volatile, so must this be)
OutCompilerData->InheritedFlags |= (CompiledDataEntries[SubDataID.Value].AccumulatedFlags & EMovieSceneSequenceFlags::InheritedFlags);
OutCompilerData->AccumulatedMask |= SubEntry.AccumulatedMask;
FSubSequenceItMetaData* MetaData = &ItMetaData.FindOrAdd(SubSequenceID);
const bool bWasEvaluatedLastFrame = MetaData->LastIterIndex != INDEX_NONE && MetaData->LastIterIndex == ItIndex-1;
if (SubEntry.CompiledFlags.bParentSequenceRequiresLowerFence && bWasEvaluatedLastFrame == false)
{
OutCompilerData->DeterminismData.Fences.Add(DiscreteInclusiveLower(SubSequenceIt.Range()));
}
if (SubEntry.CompiledFlags.bParentSequenceRequiresUpperFence)
{
MetaData->TrailingFence = DiscreteExclusiveUpper(SubSequenceIt.Range());
}
// Add determinism fences for boundary conditions
if (!SubData->OuterToInnerTransform.IsLinear() && (SubEntry.CompiledFlags.bParentSequenceRequiresUpperFence || SubEntry.CompiledFlags.bParentSequenceRequiresLowerFence) )
{
SubData->OuterToInnerTransform.ExtractBoundariesWithinRange(SubSequenceIt.Range().GetLowerBoundValue(), SubSequenceIt.Range().GetUpperBoundValue(), [OutCompilerData](FFrameTime FrameTime)
{
OutCompilerData->DeterminismData.Fences.Add(FrameTime.FrameNumber);
return true;
});
}
MetaData->LastIterIndex = ItIndex;
}
}
for (TPair<FMovieSceneSequenceID, FSubSequenceItMetaData>& Pair : ItMetaData)
{
if (Pair.Value.LastIterIndex == ItIndex-1 && Pair.Value.TrailingFence.IsSet())
{
OutCompilerData->DeterminismData.Fences.Add(Pair.Value.TrailingFence.GetValue());
Pair.Value.TrailingFence.Reset();
}
}
}
}
void UMovieSceneCompiledDataManager::CompileTrackTemplateField(FMovieSceneCompiledDataEntry* OutEntry, const FMovieSceneSequenceHierarchy& Hierarchy, FMovieSceneGatheredCompilerData* InCompilerData)
{
if (!EnumHasAnyFlags(InCompilerData->AccumulatedMask, EMovieSceneSequenceCompilerMask::EvaluationTemplate))
{
TrackTemplateFields.Remove(OutEntry->DataID.Value);
return;
}
FMovieSceneEvaluationField* TrackTemplateField = &TrackTemplateFields.FindOrAdd(OutEntry->DataID.Value);
// Wipe the current evaluation field for the template
*TrackTemplateField = FMovieSceneEvaluationField();
InCompilerData->AccumulatedMask |= EMovieSceneSequenceCompilerMask::EvaluationTemplateField;
TArray<FCompileOnTheFlyData> CompileData;
for (FMovieSceneEvaluationTreeRangeIterator It(InCompilerData->TrackTemplates); It; ++It)
{
CompileData.Reset();
TRange<FFrameNumber> FieldRange = It.Range();
for (const FCompileOnTheFlyData& TrackData : InCompilerData->TrackTemplates.GetAllData(It.Node()))
{
CompileData.Add(TrackData);
}
// Sort the compilation data based on (in order):
// 1. Group
// 2. Hierarchical bias
// 3. Evaluation priority
CompileData.Sort(SortPredicate);
// Generate the evaluation group by gathering initialization and evaluation ptrs for each unique group
FMovieSceneEvaluationGroup EvaluationGroup;
PopulateEvaluationGroup(CompileData, &EvaluationGroup);
// Compute meta data for this segment
TMovieSceneEvaluationTreeDataIterator<FMovieSceneSubSequenceTreeEntry> SubSequences = Hierarchy.GetTree().GetAllData(Hierarchy.GetTree().IterateFromLowerBound(FieldRange.GetLowerBound()).Node());
FMovieSceneEvaluationMetaData MetaData;
PopulateMetaData(Hierarchy, CompileData, SubSequences, &MetaData);
TrackTemplateField->Add(FieldRange, MoveTemp(EvaluationGroup), MoveTemp(MetaData));
}
}
void UMovieSceneCompiledDataManager::PopulateEvaluationGroup(const TArray<FCompileOnTheFlyData>& SortedCompileData, FMovieSceneEvaluationGroup* OutGroup)
{
check(OutGroup);
if (SortedCompileData.Num() == 0)
{
return;
}
static TArray<FMovieSceneFieldEntry_EvaluationTrack> InitTrackLUT;
static TArray<FMovieSceneFieldEntry_ChildTemplate> InitSectionLUT;
static TArray<FMovieSceneFieldEntry_EvaluationTrack> EvalTrackLUT;
static TArray<FMovieSceneFieldEntry_ChildTemplate> EvalSectionLUT;
InitTrackLUT.Reset();
InitSectionLUT.Reset();
EvalTrackLUT.Reset();
EvalSectionLUT.Reset();
// Now iterate the tracks and insert indices for initialization and evaluation
FName LastEvaluationGroup = SortedCompileData[0].EvaluationGroup;
int32 Index = 0;
while (Index < SortedCompileData.Num())
{
const FCompileOnTheFlyData& Data = SortedCompileData[Index];
// Check for different evaluation groups
if (Data.EvaluationGroup != LastEvaluationGroup)
{
// If we're now in a different flush group, add the ptrs to the group
AddPtrsToGroup(OutGroup, InitTrackLUT, InitSectionLUT, EvalTrackLUT, EvalSectionLUT);
}
LastEvaluationGroup = Data.EvaluationGroup;
// Add all subsequent entries that relate to the same track
FMovieSceneEvaluationFieldTrackPtr MatchTrack = Data.Track;
uint16 NumChildren = 0;
for ( ; Index < SortedCompileData.Num() && SortedCompileData[Index].Track == MatchTrack; ++Index)
{
if (SortedCompileData[Index].Child.ChildIndex != uint16(-1))
{
++NumChildren;
// If this track requires initialization, add it to the init array
if (Data.bRequiresInit)
{
InitSectionLUT.Add(SortedCompileData[Index].Child);
}
EvalSectionLUT.Add(SortedCompileData[Index].Child);
}
}
FMovieSceneFieldEntry_EvaluationTrack Entry{ Data.Track, NumChildren };
if (Data.bRequiresInit)
{
InitTrackLUT.Add(Entry);
}
EvalTrackLUT.Add(Entry);
}
AddPtrsToGroup(OutGroup, InitTrackLUT, InitSectionLUT, EvalTrackLUT, EvalSectionLUT);
}
void UMovieSceneCompiledDataManager::PopulateMetaData(const FMovieSceneSequenceHierarchy& RootHierarchy, const TArray<FCompileOnTheFlyData>& SortedCompileData, TMovieSceneEvaluationTreeDataIterator<FMovieSceneSubSequenceTreeEntry> SubSequences, FMovieSceneEvaluationMetaData* OutMetaData)
{
check(OutMetaData);
OutMetaData->Reset();
uint16 SetupIndex = 0;
uint16 TearDownIndex = 0;
for (const FCompileOnTheFlyData& CompileData : SortedCompileData)
{
if (CompileData.bRequiresInit)
{
uint32 ChildIndex = CompileData.Child.ChildIndex == uint16(-1) ? uint32(-1) : CompileData.Child.ChildIndex;
FMovieSceneEvaluationKey TrackKey(CompileData.Track.SequenceID, CompileData.Track.TrackIdentifier, ChildIndex);
OutMetaData->ActiveEntities.Add(FMovieSceneOrderedEvaluationKey{ TrackKey, SetupIndex++, (CompileData.bPriorityTearDown ? TearDownIndex : uint16(MAX_uint16-TearDownIndex)) });
++TearDownIndex;
}
}
// Then all the eval tracks
for (const FCompileOnTheFlyData& CompileData : SortedCompileData)
{
if (!CompileData.bRequiresInit)
{
uint32 ChildIndex = CompileData.Child.ChildIndex == uint16(-1) ? uint32(-1) : CompileData.Child.ChildIndex;
FMovieSceneEvaluationKey TrackKey(CompileData.Track.SequenceID, CompileData.Track.TrackIdentifier, ChildIndex);
OutMetaData->ActiveEntities.Add(FMovieSceneOrderedEvaluationKey{ TrackKey, SetupIndex++, (CompileData.bPriorityTearDown ? TearDownIndex : uint16(MAX_uint16-TearDownIndex)) });
++TearDownIndex;
}
}
Algo::SortBy(OutMetaData->ActiveEntities, &FMovieSceneOrderedEvaluationKey::Key);
{
OutMetaData->ActiveSequences.Reset();
OutMetaData->ActiveSequences.Add(MovieSceneSequenceID::Root);
for (const FMovieSceneSubSequenceTreeEntry& SubSequenceEntry : SubSequences)
{
OutMetaData->ActiveSequences.Add(SubSequenceEntry.SequenceID);
}
OutMetaData->ActiveSequences.Sort();
}
}
void UMovieSceneCompiledDataManager::CompileTrack(FMovieSceneCompiledDataEntry* OutEntry, const FMovieSceneBinding* ObjectBinding, UMovieSceneTrack* Track, const FTrackGatherParameters& Params, TSet<FGuid>* OutCompiledSignatures, FMovieSceneGatheredCompilerData* OutCompilerData)
{
using namespace UE::MovieScene;
check(Track);
check(OutCompiledSignatures);
const bool bTrackMatchesFlags = ( Params.Flags == ESectionEvaluationFlags::None )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PreRoll) && Track->EvalOptions.bEvaluateInPreroll )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PostRoll) && Track->EvalOptions.bEvaluateInPostroll );
if (!bTrackMatchesFlags)
{
return;
}
if (Track->IsEvalDisabled())
{
return;
}
UMovieSceneSequence* Sequence = OutEntry->GetSequence();
check(Sequence);
// -------------------------------------------------------------------------------------------------------------------------------------
// Step 1 - ensure that track templates exist for any track that implements IMovieSceneTrackTemplateProducer
FMovieSceneTrackIdentifier TrackIdentifier;
FMovieSceneEvaluationTemplate* TrackTemplate = nullptr;
if (const IMovieSceneTrackTemplateProducer* TrackTemplateProducer = Cast<const IMovieSceneTrackTemplateProducer>(Track))
{
TrackTemplate = &TrackTemplates.FindOrAdd(OutEntry->DataID.Value);
check(TrackTemplate);
TrackIdentifier = TrackTemplate->GetLedger().FindTrackIdentifier(Track->GetSignature());
if (!TrackIdentifier)
{
// If the track doesn't exist - we need to generate it from scratch
FMovieSceneTrackCompilerArgs Args(Track, &Params.TemplateGenerator);
if (ObjectBinding)
{
Args.ObjectBindingId = ObjectBinding->GetObjectGuid();
}
Args.DefaultCompletionMode = Sequence->DefaultCompletionMode;
TrackTemplateProducer->GenerateTemplate(Args);
TrackIdentifier = TrackTemplate->GetLedger().FindTrackIdentifier(Track->GetSignature());
}
if (TrackIdentifier)
{
OutCompiledSignatures->Add(Track->GetSignature());
}
OutCompilerData->AccumulatedMask |= EMovieSceneSequenceCompilerMask::EvaluationTemplate;
}
// -------------------------------------------------------------------------------------------------------------------------------------
// Step 2 - let the track or its sections add determinism fences
if (IMovieSceneDeterminismSource* DeterminismSource = Cast<IMovieSceneDeterminismSource>(Track))
{
DeterminismSource->PopulateDeterminismData(OutCompilerData->DeterminismData, TRange<FFrameNumber>::All());
}
const FMovieSceneTrackEvaluationField& EvaluationField = Track->GetEvaluationField();
const EMovieSceneCompletionMode DefaultCompletionMode = Sequence->DefaultCompletionMode;
const bool bAddKeepStateDeterminismFences = CVarAddKeepStateDeterminismFences.GetValueOnGameThread();
for (const FMovieSceneTrackEvaluationFieldEntry& Entry : EvaluationField.Entries)
{
if (bAddKeepStateDeterminismFences && Entry.Section)
{
// If a section is KeepState, we need to make sure to evaluate it on its last frame so that the value that "sticks" is correct.
const TRange<FFrameNumber> SectionRange = Entry.Section->GetRange();
const EMovieSceneCompletionMode SectionCompletionMode = Entry.Section->GetCompletionMode();
if (SectionRange.HasUpperBound() &&
(SectionCompletionMode == EMovieSceneCompletionMode::KeepState ||
(SectionCompletionMode == EMovieSceneCompletionMode::ProjectDefault && DefaultCompletionMode == EMovieSceneCompletionMode::KeepState)))
{
// We simply use the end time of the section for the fence, regardless of whether it's inclusive or exclusive.
// When exclusive, the ECS system will query entities just before that time, but still pass that time for
// evaluation purposes, so we will get the correct evaluated values.
const FFrameNumber FenceTime(SectionRange.GetUpperBoundValue());
OutCompilerData->DeterminismData.Fences.Add(FenceTime);
}
}
IMovieSceneDeterminismSource* DeterminismSource = Cast<IMovieSceneDeterminismSource>(Entry.Section);
if (DeterminismSource)
{
DeterminismSource->PopulateDeterminismData(OutCompilerData->DeterminismData, Entry.Range);
}
}
}
void UMovieSceneCompiledDataManager::GatherTrack(const FMovieSceneBinding* ObjectBinding, UMovieSceneTrack* Track, const FTrackGatherParameters& Params, const FMovieSceneEvaluationTemplate* TrackTemplate, FMovieSceneGatheredCompilerData* OutCompilerData) const
{
using namespace UE::MovieScene;
check(Track);
const bool bTrackMatchesFlags = ( Params.Flags == ESectionEvaluationFlags::None )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PreRoll) && Track->EvalOptions.bEvaluateInPreroll )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PostRoll) && Track->EvalOptions.bEvaluateInPostroll );
if (!bTrackMatchesFlags)
{
return;
}
if (Track->IsEvalDisabled())
{
return;
}
// Some tracks could want to do some custom pre-compilation things.
FMovieSceneTrackPreCompileResult PreCompileResult;
Track->PreCompile(PreCompileResult);
const FMovieSceneTrackEvaluationField& EvaluationField = Track->GetEvaluationField();
// -------------------------------------------------------------------------------------------------------------------------------------
// Step 1 - Handle any entity producers that exist within the field
if (OutCompilerData->EntityField)
{
FMovieSceneEntityComponentFieldBuilder FieldBuilder(OutCompilerData->EntityField);
if (ObjectBinding)
{
FieldBuilder.GetSharedMetaData().ObjectBindingID = ObjectBinding->GetObjectGuid();
}
for (UObject* Decoration : Track->GetDecorations())
{
if (IMovieSceneEntityProvider* Provider = Cast<IMovieSceneEntityProvider>(Decoration))
{
FMovieSceneEvaluationFieldEntityMetaData MetaData(PreCompileResult.DefaultMetaData);
MetaData.bEvaluateInSequencePreRoll = Track->EvalOptions.bEvaluateInPreroll;
MetaData.bEvaluateInSequencePostRoll = Track->EvalOptions.bEvaluateInPostroll;
MetaData.Condition = Track->ConditionContainer.Condition;
Provider->PopulateEvaluationField(Params.LocalClampRange, MetaData, &FieldBuilder);
}
}
IMovieSceneEntityProvider* TrackEntityProvider = Cast<IMovieSceneEntityProvider>(Track);
// If the track is an entity provider, allow it to add entries first
if (TrackEntityProvider)
{
FMovieSceneEvaluationFieldEntityMetaData MetaData(PreCompileResult.DefaultMetaData);
MetaData.bEvaluateInSequencePreRoll = Track->EvalOptions.bEvaluateInPreroll;
MetaData.bEvaluateInSequencePostRoll = Track->EvalOptions.bEvaluateInPostroll;
MetaData.Condition = Track->ConditionContainer.Condition;
TrackEntityProvider->PopulateEvaluationField(Params.LocalClampRange, MetaData, &FieldBuilder);
}
else for (const FMovieSceneTrackEvaluationFieldEntry& Entry : EvaluationField.Entries)
{
if (Entry.Section && Track->IsRowEvalDisabled(Entry.Section->GetRowIndex()))
{
continue;
}
IMovieSceneEntityProvider* EntityProvider = Cast<IMovieSceneEntityProvider>(Entry.Section);
if (!EntityProvider)
{
continue;
}
// This codepath should only ever execute for the highest level so we do not need to do any transformations
TRange<FFrameNumber> EffectiveRange = TRange<FFrameNumber>::Intersection(Params.LocalClampRange, Entry.Range);
if (!EffectiveRange.IsEmpty())
{
FMovieSceneEvaluationFieldEntityMetaData MetaData(PreCompileResult.DefaultMetaData);
MetaData.ForcedTime = Entry.ForcedTime;
MetaData.Flags = Entry.Flags;
MetaData.bEvaluateInSequencePreRoll = Track->EvalOptions.bEvaluateInPreroll;
MetaData.bEvaluateInSequencePostRoll = Track->EvalOptions.bEvaluateInPostroll;
MetaData.Condition = MovieSceneHelpers::GetSequenceCondition(Track, Entry.Section, true);
if (!EntityProvider->PopulateEvaluationField(EffectiveRange, MetaData, &FieldBuilder))
{
const int32 EntityIndex = FieldBuilder.FindOrAddEntity(Entry.Section, 0);
const int32 MetaDataIndex = FieldBuilder.AddMetaData(MetaData);
FieldBuilder.AddPersistentEntity(EffectiveRange, EntityIndex, MetaDataIndex);
}
}
}
}
// -------------------------------------------------------------------------------------------------------------------------------------
// Step 2 - Handle the track being a template producer
FMovieSceneTrackIdentifier TrackIdentifier = TrackTemplate ? TrackTemplate->GetLedger().FindTrackIdentifier(Track->GetSignature()) : FMovieSceneTrackIdentifier();
if (TrackIdentifier)
{
// Iterate everything in the field
for (const FMovieSceneTrackEvaluationFieldEntry& Entry : EvaluationField.Entries)
{
// Iterate all the valid ranges this translates to in the root
FMovieSceneInverseSequenceTransform SequenceToRootTransform = Params.RootToSequenceTransform.Inverse();
auto VisitWarpedRootRange = [this, &Entry, &Params, &OutCompilerData, TrackIdentifier, TrackTemplate, Track](TRange<FFrameTime> InRange)
{
TRange<FFrameNumber> ClampedRangeRoot = Params.ClampRoot(ConvertToDiscreteRange(InRange));
UMovieSceneSection* Section = Entry.Section;
if (Section && Track->IsRowEvalDisabled(Section->GetRowIndex()))
{
return true;
}
if (ClampedRangeRoot.IsEmpty())
{
return true;
}
const FMovieSceneEvaluationTrack* EvaluationTrack = TrackTemplate->FindTrack(TrackIdentifier);
check(EvaluationTrack);
// Get the correct template for the sub sequence
FCompileOnTheFlyData CompileData;
CompileData.Track = FMovieSceneEvaluationFieldTrackPtr(Params.SequenceID, TrackIdentifier);
CompileData.EvaluationPriority = EvaluationTrack->GetEvaluationPriority();
CompileData.EvaluationGroup = EvaluationTrack->GetEvaluationGroup();
CompileData.GroupEvaluationPriority = GetMovieSceneModule().GetEvaluationGroupParameters(CompileData.EvaluationGroup).EvaluationPriority;
CompileData.HierarchicalBias = Params.HierarchicalBias;
CompileData.bPriorityTearDown = EvaluationTrack->HasTearDownPriority();
auto FindChildWithSection = [Section](const FMovieSceneEvalTemplatePtr& ChildTemplate)
{
return ChildTemplate.IsValid() && ChildTemplate->GetSourceSection() == Section;
};
const int32 ChildTemplateIndex = Section ? EvaluationTrack->GetChildTemplates().IndexOfByPredicate(FindChildWithSection) : INDEX_NONE;
if (ChildTemplateIndex != INDEX_NONE)
{
check(ChildTemplateIndex >= 0 && ChildTemplateIndex < TNumericLimits<uint16>::Max());
ESectionEvaluationFlags Flags = Params.Flags == ESectionEvaluationFlags::None ? Entry.Flags : Params.Flags;
if (EnumHasAnyFlags(Params.AccumulatedFlags, EMovieSceneSubSectionFlags::OverrideRestoreState))
{
Flags |= ESectionEvaluationFlags::ForceRestoreState;
}
else if (EnumHasAnyFlags(Params.AccumulatedFlags, EMovieSceneSubSectionFlags::OverrideKeepState))
{
Flags |= ESectionEvaluationFlags::ForceKeepState;
}
CompileData.ChildPriority = Entry.LegacySortOrder;
CompileData.Child = FMovieSceneFieldEntry_ChildTemplate((uint16)ChildTemplateIndex, Flags, Entry.ForcedTime);
CompileData.bRequiresInit = EvaluationTrack->GetChildTemplate(ChildTemplateIndex).RequiresInitialization();
}
else
{
CompileData.ChildPriority = 0;
CompileData.Child = FMovieSceneFieldEntry_ChildTemplate{};
CompileData.bRequiresInit = false;
}
OutCompilerData->TrackTemplates.Add(ClampedRangeRoot, CompileData);
return true;
};
Params.TransformLocalRange(Entry.Range, VisitWarpedRootRange);
}
}
}
bool UMovieSceneCompiledDataManager::CompileHierarchy(UMovieSceneSequence* Sequence, FMovieSceneSequenceHierarchy* InOutHierarchy, EMovieSceneServerClientMask InNetworkMask)
{
FGatherParameters Params;
Params.NetworkMask = InNetworkMask;
return CompileHierarchy(Sequence, Params, InOutHierarchy);
}
bool UMovieSceneCompiledDataManager::CompileHierarchy(UMovieSceneSequence* Sequence, const FGatherParameters& Params, FMovieSceneSequenceHierarchy* InOutHierarchy)
{
using namespace UE::MovieScene;
UE::MovieScene::FSubSequencePath RootPath;
const FGatherParameters* ParamsToUse = &Params;
bool bContainsTimeWarp = false;
if (Params.SequenceID == MovieSceneSequenceID::Root)
{
UMovieSceneTimeWarpDecoration* TimeWarp = Sequence->GetMovieScene()->FindDecoration<UMovieSceneTimeWarpDecoration>();
if (TimeWarp)
{
FMovieSceneSequenceTransform TimeWarpTransform = TimeWarp->GenerateTransform();
// Don't do anything for identity transforms
if (!TimeWarpTransform.IsIdentity())
{
InOutHierarchy->SetRootTransform(FMovieSceneSequenceTransform(MoveTemp(TimeWarpTransform)));
bContainsTimeWarp = true;
}
}
}
// Compile all the sub data for every part of the hierarchy
const bool bContainsSubSequences = GenerateSubSequenceData(Sequence, *ParamsToUse, FMovieSceneEvaluationOperand(), &RootPath, InOutHierarchy);
// Populate the sub sequence tree that defines which sub sequences happen at a given time
PopulateSubSequenceTree(Sequence, *ParamsToUse, &RootPath, InOutHierarchy);
return bContainsSubSequences || bContainsTimeWarp;
}
bool UMovieSceneCompiledDataManager::GenerateSubSequenceData(UMovieSceneSequence* SubSequence, const FGatherParameters& Params, const FMovieSceneEvaluationOperand& Operand, UE::MovieScene::FSubSequencePath* RootPath, FMovieSceneSequenceHierarchy* InOutHierarchy)
{
using namespace UE::MovieScene;
UMovieScene* MovieScene = SubSequence ? SubSequence->GetMovieScene() : nullptr;
if (!MovieScene)
{
return false;
}
check(RootPath && InOutHierarchy);
bool bContainsSubSequences = false;
for (UMovieSceneTrack* Track : MovieScene->GetTracks())
{
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
bContainsSubSequences |= GenerateSubSequenceData(SubTrack, Params, Operand, RootPath, InOutHierarchy);
}
}
for (const FMovieSceneBinding& ObjectBinding : ((const UMovieScene*)MovieScene)->GetBindings())
{
for (UMovieSceneTrack* Track : ObjectBinding.GetTracks())
{
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
const FMovieSceneEvaluationOperand ChildOperand(Params.SequenceID, ObjectBinding.GetObjectGuid());
bContainsSubSequences |= GenerateSubSequenceData(SubTrack, Params, ChildOperand, RootPath, InOutHierarchy);
}
}
}
return bContainsSubSequences;
}
bool UMovieSceneCompiledDataManager::GenerateSubSequenceData(UMovieSceneSubTrack* SubTrack, const FGatherParameters& Params, const FMovieSceneEvaluationOperand& Operand, UE::MovieScene::FSubSequencePath* RootPath, FMovieSceneSequenceHierarchy* InOutHierarchy)
{
using namespace UE::MovieScene;
bool bContainsSubSequences = false;
check(SubTrack && RootPath);
const FMovieSceneSequenceID ParentSequenceID = Params.SequenceID;
for (UMovieSceneSection* Section : SubTrack->GetAllSections())
{
if (SubTrack->IsRowEvalDisabled(Section->GetRowIndex()))
{
continue;
}
UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Section);
if (!SubSection)
{
continue;
}
// Note: we always compile FMovieSceneSubSequenceData for all entries of a hierarchy, even if excluded from the network mask
// to ensure that hierarchical information is still available when emulating different network masks
UMovieSceneSequence* SubSequence = SubSection->GetSequence();
if (!SubSequence)
{
continue;
}
UMovieScene* MovieScene = SubSequence->GetMovieScene();
if (!MovieScene)
{
continue;
}
const FMovieSceneSequenceID InnerSequenceID = RootPath->ResolveChildSequenceID(SubSection->GetSequenceID());
FSubSequenceInstanceDataParams InstanceParams{ InnerSequenceID, Operand };
FMovieSceneSubSequenceData NewSubData = SubSection->GenerateSubSequenceData(InstanceParams);
// LocalClampRange here is in SubTrack's space, so we need to multiply that by the OuterToInnerTransform
// (which is the same as RootToSequenceTransform here before we transform it)
TRange<FFrameNumber> InnerClampRange = (Params.LocalClampRange.GetLowerBound().IsOpen() || Params.LocalClampRange.GetUpperBound().IsOpen())
? Params.LocalClampRange
: ConvertToDiscreteRange(NewSubData.OuterToInnerTransform.ComputeTraversedHull(Params.LocalClampRange));
// Put the root play range in the new root space
NewSubData.PlayRange = TRange<FFrameNumber>::Intersection(InnerClampRange, NewSubData.PlayRange.Value);
NewSubData.RootToSequenceTransform = NewSubData.RootToSequenceTransform * Params.RootToSequenceTransform;
#if WITH_EDITORONLY_DATA
NewSubData.RootToUnwarpedLocalTransform = NewSubData.RootToUnwarpedLocalTransform * Params.RootToUnwarpedLocalTransform;
#endif
NewSubData.HierarchicalBias = Params.HierarchicalBias + NewSubData.HierarchicalBias;
NewSubData.AccumulatedFlags = UE::MovieScene::AccumulateChildSubSectionFlags(Params.AccumulatedFlags, NewSubData.AccumulatedFlags);
#if WITH_EDITORONLY_DATA
NewSubData.StartTimeBreadcrumbs.CombineWithOuterBreadcrumbs(Params.StartTimeBreadcrumbs);
NewSubData.EndTimeBreadcrumbs.CombineWithOuterBreadcrumbs(Params.EndTimeBreadcrumbs);
#endif // WITH_EDITORONLY_DATA
// Add the sub data to the root hierarchy
InOutHierarchy->Add(NewSubData, InnerSequenceID, ParentSequenceID);
// Iterate into the sub sequence
FGatherParameters SubParams = Params.CreateForSubData(NewSubData, InnerSequenceID);
// This is a bit of hack to make sure that LocalClampRange gets sent through to the next GenerateSubSequenceData call,
// but we do not set RootClampRange because it would be ambiguous to do so w.r.t looping sub sequences
SubParams.LocalClampRange = NewSubData.PlayRange.Value;
RootPath->PushGeneration(InnerSequenceID, NewSubData.DeterministicSequenceID);
GenerateSubSequenceData(SubSequence, SubParams, Operand, RootPath, InOutHierarchy);
RootPath->PopGenerations(1);
bContainsSubSequences = true;
}
return bContainsSubSequences;
}
void UMovieSceneCompiledDataManager::PopulateSubSequenceTree(UMovieSceneSequence* SubSequence, const FGatherParameters& Params, UE::MovieScene::FSubSequencePath* RootPath, FMovieSceneSequenceHierarchy* InOutHierarchy)
{
UMovieScene* MovieScene = SubSequence ? SubSequence->GetMovieScene() : nullptr;
if (!MovieScene)
{
return;
}
check(RootPath && InOutHierarchy);
for (UMovieSceneTrack* Track : MovieScene->GetTracks())
{
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
PopulateSubSequenceTree(SubTrack, Params, RootPath, InOutHierarchy);
}
}
for (const FMovieSceneBinding& ObjectBinding : ((const UMovieScene*)MovieScene)->GetBindings())
{
for (UMovieSceneTrack* Track : ObjectBinding.GetTracks())
{
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
PopulateSubSequenceTree(SubTrack, Params, RootPath, InOutHierarchy);
}
}
}
}
void UMovieSceneCompiledDataManager::PopulateSubSequenceTree(UMovieSceneSubTrack* SubTrack, const FGatherParameters& Params, UE::MovieScene::FSubSequencePath* RootPath, FMovieSceneSequenceHierarchy* InOutHierarchy)
{
using namespace UE::MovieScene;
check(SubTrack && RootPath);
const bool bTrackMatchesFlags = ( Params.Flags == ESectionEvaluationFlags::None )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PreRoll) && SubTrack->EvalOptions.bEvaluateInPreroll )
|| ( EnumHasAnyFlags(Params.Flags, ESectionEvaluationFlags::PostRoll) && SubTrack->EvalOptions.bEvaluateInPostroll );
if (!bTrackMatchesFlags)
{
return;
}
if (SubTrack->IsEvalDisabled())
{
return;
}
UMovieSceneSequence* OuterSequence = SubTrack->GetTypedOuter<UMovieSceneSequence>();
if (!OuterSequence)
{
return;
}
for (const FMovieSceneTrackEvaluationFieldEntry& Entry : SubTrack->GetEvaluationField().Entries)
{
UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Entry.Section);
if (!SubSection || SubSection->GetSequence() == nullptr || SubSection->GetSequence()->GetMovieScene() == nullptr)
{
continue;
}
if (SubTrack->IsRowEvalDisabled(SubSection->GetRowIndex()))
{
continue;
}
EMovieSceneServerClientMask NewMask = Params.NetworkMask & SubSection->GetNetworkMask();
if (NewMask == EMovieSceneServerClientMask::None)
{
continue;
}
InOutHierarchy->AccumulateNetworkMask(SubSection->GetNetworkMask());
const FMovieSceneSequenceID SubSequenceID = RootPath->ResolveChildSequenceID(SubSection->GetSequenceID());
const FMovieSceneSubSequenceData* SubData = InOutHierarchy->FindSubData(SubSequenceID);
checkf(SubData, TEXT("Unable to locate sub-data for a sub section that appears in the track's evaluation field - this indicates that the section is being evaluated even though it is not active"));
auto AddRange = [&Params, &Entry, &RootPath, InOutHierarchy, NewMask, SubData, SubSequenceID](TRange<FFrameTime> Range)
{
TRange<FFrameNumber> FrameRange = Params.ClampRoot(ConvertToDiscreteRange(Range));
if (!FrameRange.IsEmpty())
{
FGatherParameters SubParams = Params.CreateForSubData(*SubData, SubSequenceID);
SubParams.SetClampRange(FrameRange);
SubParams.Flags |= Entry.Flags;
SubParams.NetworkMask = NewMask;
const ESectionEvaluationFlags SubEntryFlags = Entry.Flags | Params.Flags;
InOutHierarchy->AddRange(FrameRange, SubSequenceID, SubEntryFlags);
// Recurse into the sub sequence
RootPath->PushGeneration(SubSequenceID, SubData->DeterministicSequenceID);
{
PopulateSubSequenceTree(SubData->GetSequence(), SubParams, RootPath, InOutHierarchy);
}
RootPath->PopGenerations(1);
}
return true;
};
Params.TransformLocalRange(Entry.Range, AddRange);
}
}
TOptional<FFrameNumber> UMovieSceneCompiledDataManager::GetLoopingSubSectionEndTime(const UMovieSceneSequence* InRootSequence, const UMovieSceneSubSection* SubSection, const FGatherParameters& Params)
{
using namespace UE::MovieScene;
TRangeBound<FFrameNumber> SectionRangeEnd = SubSection->SectionRange.GetUpperBound();
if (!SectionRangeEnd.IsOpen())
{
return DiscreteExclusiveUpper(SectionRangeEnd);
}
// This section is open ended... we don't want to compile its sub-sequence in an infinite loop so we'll bound
// that by the playback end of is own sequence.
if (const UMovieScene* MovieScene = InRootSequence->GetMovieScene())
{
const TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
if (!PlaybackRange.GetUpperBound().IsOpen())
{
return DiscreteExclusiveUpper(PlaybackRange.GetUpperBound());
}
}
// Sadly, the root sequence is also open ended, so we effectively would need to loop the sub-sequence
// indefinitely... we don't support that yet.
return TOptional<FFrameNumber>();
}