// Copyright Epic Games, Inc. All Rights Reserved. #include "Evaluation/Instances/MovieSceneTrackEvaluator.h" #include "Containers/SortedMap.h" #include "IMovieScenePlayer.h" #include "MovieSceneSequence.h" #include "Sections/MovieSceneSubSection.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "Evaluation/PreAnimatedState/MovieScenePreAnimatedCaptureSources.h" #include "Stats/Stats.h" #include "IMovieSceneModule.h" #include "Algo/Sort.h" #include "Algo/Find.h" DECLARE_CYCLE_STAT(TEXT("Template Evaluation Cost"), MovieSceneEval_TrackEvaluator, STATGROUP_MovieSceneEval); DECLARE_CYCLE_STAT(TEXT("Gather Entries For Frame"), MovieSceneEval_GatherEntries, STATGROUP_MovieSceneEval); DECLARE_CYCLE_STAT(TEXT("Call Setup() and TearDown()"), MovieSceneEval_CallSetupTearDown, STATGROUP_MovieSceneEval); DECLARE_CYCLE_STAT(TEXT("Evaluate Group"), MovieSceneEval_EvaluateGroup, STATGROUP_MovieSceneEval); /** Scoped helper class that facilitates the delayed restoration of preanimated state for specific evaluation keys */ struct FDelayedPreAnimatedStateRestore { FDelayedPreAnimatedStateRestore(IMovieScenePlayer& InPlayer) : Player(InPlayer) {} ~FDelayedPreAnimatedStateRestore() { RestoreNow(); } void Add(FMovieSceneEvaluationKey Key) { KeysToRestore.Add(Key); } void RestoreNow() { using namespace UE::MovieScene; UMovieSceneEntitySystemLinker* Linker = Player.GetSharedPlaybackState()->GetLinker(); FPreAnimatedTemplateCaptureSources* TemplateMetaData = Linker->PreAnimatedState.GetTemplateMetaData(); if (TemplateMetaData) { const FRootInstanceHandle RootInstanceHandle = Player.GetEvaluationTemplate().GetRootInstanceHandle(); for (FMovieSceneEvaluationKey Key : KeysToRestore) { TemplateMetaData->StopTrackingCaptureSource(Key, RootInstanceHandle); } } KeysToRestore.Reset(); } private: /** The movie scene player to restore with */ IMovieScenePlayer& Player; /** The array of keys to restore */ TArray KeysToRestore; }; FMovieSceneTrackEvaluator::FMovieSceneTrackEvaluator(UMovieSceneSequence* InRootSequence, FMovieSceneCompiledDataID InRootCompiledDataID, UMovieSceneCompiledDataManager* InCompiledDataManager) : RootSequence(InRootSequence) , RootCompiledDataID(InRootCompiledDataID) , RootID(MovieSceneSequenceID::Root) , CompiledDataManager(InCompiledDataManager) { CachedReallocationVersion = 0; } FMovieSceneTrackEvaluator::~FMovieSceneTrackEvaluator() { const bool bHasFinished = (GExitPurge || ThisFrameMetaData.ActiveEntities.Num() == 0); if (!bHasFinished) { UE_LOG(LogMovieSceneECS, Verbose, TEXT("Evaluator instance being torn down without calling Finish (ThisFrameMetaData has data)")); } } void FMovieSceneTrackEvaluator::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(CompiledDataManager); } void FMovieSceneTrackEvaluator::Finish(IMovieScenePlayer& Player) { Swap(ThisFrameMetaData, LastFrameMetaData); ThisFrameMetaData.Reset(); ConstructEvaluationPtrCache(); CallSetupTearDown(Player); } void FMovieSceneTrackEvaluator::Evaluate(FMovieSceneContext Context, IMovieScenePlayer& Player, FMovieSceneSequenceID InOverrideRootID) { SCOPE_CYCLE_COUNTER(MovieSceneEval_TrackEvaluator); Swap(ThisFrameMetaData, LastFrameMetaData); ThisFrameMetaData.Reset(); ConstructEvaluationPtrCache(); if (RootID != InOverrideRootID) { // Tear everything down if we're evaluating a different root sequence CallSetupTearDown(Player); LastFrameMetaData.Reset(); } UMovieSceneSequence* OverrideRootSequence = GetSequence(InOverrideRootID); if (!OverrideRootSequence) { CallSetupTearDown(Player); return; } SCOPE_CYCLE_UOBJECT(ContextScope, OverrideRootSequence); const FMovieSceneEvaluationGroup* GroupToEvaluate = SetupFrame(OverrideRootSequence, InOverrideRootID, Context); if (!GroupToEvaluate) { CallSetupTearDown(Player); return; } // Cause stale tracks to not restore until after evaluation. This fixes issues when tracks that are set to 'Restore State' are regenerated, causing the state to be restored then re-animated by the new track FDelayedPreAnimatedStateRestore DelayedRestore(Player); // Run the post root evaluate steps which invoke tear downs for anything no longer evaluated. // Do this now to ensure they don't undo any of the current frame's execution tokens CallSetupTearDown(Player, &DelayedRestore); // Ensure any null objects are not cached if (FMovieSceneEvaluationState* State = Player.GetEvaluationState()) { State->InvalidateExpiredObjects(); } // Accumulate execution tokens into this structure EvaluateGroup(*GroupToEvaluate, Context, Player); // Process execution tokens ExecutionTokens.Apply(Context, Player); } void FMovieSceneTrackEvaluator::ConstructEvaluationPtrCache() { // Cache all the pointers needed for this frame const uint32 CurrentReallocationVersion = CompiledDataManager->GetReallocationVersion(); if (CachedPtrs.Num() == 0 || CurrentReallocationVersion > CachedReallocationVersion) { check(CompiledDataManager); const FMovieSceneSequenceHierarchy* RootHierarchy = CompiledDataManager->FindHierarchy(RootCompiledDataID); if (RootHierarchy) { // Cache all sub-sequence ptrs for (const TTuple& Pair : RootHierarchy->AllSubSequenceData()) { UMovieSceneSequence* SubSequence = Pair.Value.GetSequence(); FMovieSceneCompiledDataID SubDataID = CompiledDataManager->FindDataID(SubSequence); const FMovieSceneEvaluationTemplate* SubTemplate = CompiledDataManager->FindTrackTemplate(SubDataID); if (SubTemplate) { CachedPtrs.Add(Pair.Key, FCachedPtrs{ SubSequence, SubTemplate, &Pair.Value }); } } } // Find the root template from the template store const FMovieSceneEvaluationTemplate* RootTemplate = CompiledDataManager->FindTrackTemplate(RootCompiledDataID); if (RootTemplate) { CachedPtrs.Add(MovieSceneSequenceID::Root, FCachedPtrs{ RootSequence.Get(), RootTemplate, nullptr }); } } } const FMovieSceneEvaluationGroup* FMovieSceneTrackEvaluator::SetupFrame(UMovieSceneSequence* OverrideRootSequence, FMovieSceneSequenceID InOverrideRootID, FMovieSceneContext Context) { const FMovieSceneSequenceHierarchy* RootHierarchy = CompiledDataManager->FindHierarchy(RootCompiledDataID); check(OverrideRootSequence); RootID = InOverrideRootID; RootOverridePath.Reset(InOverrideRootID, RootHierarchy); const FMovieSceneEvaluationField* OverrideRootField = nullptr; FFrameTime RootTime = Context.GetEvaluationFieldTime(); if (InOverrideRootID == MovieSceneSequenceID::Root) { OverrideRootField = CompiledDataManager->FindTrackTemplateField(RootCompiledDataID); } else { check(RootHierarchy); // Evaluate Sub Sequences in Isolation is turned on FMovieSceneCompiledDataID OverrideRootDataID = CompiledDataManager->FindDataID(OverrideRootSequence); OverrideRootField = CompiledDataManager->FindTrackTemplateField(OverrideRootDataID); if (const FMovieSceneSubSequenceData* OverrideSubData = RootHierarchy->FindSubData(InOverrideRootID)) { RootTime *= OverrideSubData->RootToSequenceTransform; } } if (OverrideRootField == nullptr) { return nullptr; } // The one that we want to evaluate is either the first or last index in the range. // FieldRange is always of the form [First, Last+1) const int32 TemplateFieldIndex = OverrideRootField->GetSegmentFromTime(RootTime.FloorToFrame()); if (TemplateFieldIndex != INDEX_NONE) { // Set meta-data ThisFrameMetaData = OverrideRootField->GetMetaData(TemplateFieldIndex); return &OverrideRootField->GetGroup(TemplateFieldIndex); } return nullptr; } void FMovieSceneTrackEvaluator::EvaluateGroup(const FMovieSceneEvaluationGroup& Group, const FMovieSceneContext& RootContext, IMovieScenePlayer& Player) { MOVIESCENE_DETAILED_SCOPE_CYCLE_COUNTER(MovieSceneEval_EvaluateGroup); FPersistentEvaluationData PersistentDataProxy(Player); FMovieSceneEvaluationOperand Operand; FMovieSceneContext Context = RootContext; FMovieSceneContext SubContext = Context; int32 TrackIndex = 0; const FMovieSceneFieldEntry_ChildTemplate* Templates = Group.SectionLUT.GetData(); for (const FMovieSceneEvaluationGroupLUTIndex& Index : Group.LUTIndices) { const int32 LastInitTrack = TrackIndex + Index.NumInitPtrs; // Initialize anything that wants to be initialized first for ( ; TrackIndex < LastInitTrack; ++TrackIndex) { FMovieSceneFieldEntry_EvaluationTrack TrackEntry = Group.TrackLUT[TrackIndex]; FMovieSceneEvaluationFieldTrackPtr TrackPtr = TrackEntry.TrackPtr; // Ensure we're able to find the sequence instance in our root if we've overridden TrackPtr.SequenceID = RootOverridePath.ResolveChildSequenceID(TrackPtr.SequenceID); const FCachedPtrs& EvalPtrs = CachedPtrs.FindChecked(TrackPtr.SequenceID); const FMovieSceneEvaluationTrack* Track = EvalPtrs.Template->FindTrack(TrackPtr.TrackIdentifier); if (Track) { Operand.ObjectBindingID = Track->GetObjectBindingID(); Operand.SequenceID = TrackPtr.SequenceID; FMovieSceneEvaluationKey TrackKey(TrackPtr.SequenceID, TrackPtr.TrackIdentifier); PersistentDataProxy.SetTrackKey(TrackKey); FScopedPreAnimatedCaptureSource CaptureSource(&Player.PreAnimatedState, TrackKey, false); SubContext = Context; if (EvalPtrs.SubData) { SubContext = Context.Transform(EvalPtrs.SubData->RootToSequenceTransform, EvalPtrs.SubData->TickResolution); // Hittest against the sequence's pre and postroll ranges SubContext.ReportOuterSectionRanges(EvalPtrs.SubData->PreRollRange.Value, EvalPtrs.SubData->PostRollRange.Value); SubContext.SetHierarchicalBias(EvalPtrs.SubData->HierarchicalBias); } TArrayView ChildTemplates(Templates, TrackEntry.NumChildren); Track->Initialize(ChildTemplates, Operand, SubContext, PersistentDataProxy, Player); } Templates += TrackEntry.NumChildren; } // Then evaluate const int32 LastEvalTrack = TrackIndex + Index.NumEvalPtrs; for (; TrackIndex < LastEvalTrack; ++TrackIndex) { FMovieSceneFieldEntry_EvaluationTrack TrackEntry = Group.TrackLUT[TrackIndex]; FMovieSceneEvaluationFieldTrackPtr TrackPtr = TrackEntry.TrackPtr; // Ensure we're able to find the sequence instance in our root if we've overridden TrackPtr.SequenceID = RootOverridePath.ResolveChildSequenceID(TrackPtr.SequenceID); const FCachedPtrs& EvalPtrs = CachedPtrs.FindChecked(TrackPtr.SequenceID); const FMovieSceneEvaluationTrack* Track = EvalPtrs.Template->FindTrack(TrackPtr.TrackIdentifier); if (Track) { Operand.ObjectBindingID = Track->GetObjectBindingID(); Operand.SequenceID = TrackPtr.SequenceID; FMovieSceneEvaluationKey TrackKey(TrackPtr.SequenceID, TrackPtr.TrackIdentifier); PersistentDataProxy.SetTrackKey(TrackKey); ExecutionTokens.SetOperand(Operand); ExecutionTokens.SetCurrentScope(FMovieSceneEvaluationScope(TrackKey, EMovieSceneCompletionMode::KeepState)); SubContext = Context; if (EvalPtrs.SubData) { SubContext = Context.Transform(EvalPtrs.SubData->RootToSequenceTransform, EvalPtrs.SubData->TickResolution); // Hittest against the sequence's pre and postroll ranges SubContext.ReportOuterSectionRanges(EvalPtrs.SubData->PreRollRange.Value, EvalPtrs.SubData->PostRollRange.Value); SubContext.SetHierarchicalBias(EvalPtrs.SubData->HierarchicalBias); } TArrayView ChildTemplates(Templates, TrackEntry.NumChildren); Track->Evaluate( ChildTemplates, Operand, SubContext, PersistentDataProxy, ExecutionTokens); } Templates += TrackEntry.NumChildren; } ExecutionTokens.Apply(Context, Player); } } void FMovieSceneTrackEvaluator::CallSetupTearDown(IMovieScenePlayer& Player, FDelayedPreAnimatedStateRestore* DelayedRestore) { using namespace UE::MovieScene; MOVIESCENE_DETAILED_SCOPE_CYCLE_COUNTER(MovieSceneEval_CallSetupTearDown); UMovieSceneEntitySystemLinker* Linker = Player.GetEvaluationTemplate().GetEntitySystemLinker(); FPreAnimatedTemplateCaptureSources* TemplateMetaData = Linker->PreAnimatedState.GetTemplateMetaData(); const FRootInstanceHandle RootInstanceHandle = Player.GetEvaluationTemplate().GetRootInstanceHandle(); FPersistentEvaluationData PersistentDataProxy(Player); TArray ExpiredEntities; TArray NewEntities; ThisFrameMetaData.DiffEntities(LastFrameMetaData, &NewEntities, &ExpiredEntities); for (const FMovieSceneOrderedEvaluationKey& OrderedKey : ExpiredEntities) { FMovieSceneEvaluationKey Key = OrderedKey.Key; // Ensure we're able to find the sequence instance in our root if we've overridden Key.SequenceID = RootOverridePath.ResolveChildSequenceID(Key.SequenceID); const FCachedPtrs* EvalPtrs = CachedPtrs.Find(Key.SequenceID); if (EvalPtrs) { const FMovieSceneEvaluationTrack* Track = EvalPtrs->Template->FindTrack(Key.TrackIdentifier); const bool bStaleTrack = EvalPtrs->Template->IsTrackStale(Key.TrackIdentifier); // Track data key may be required by both tracks and sections PersistentDataProxy.SetTrackKey(Key.AsTrack()); if (Key.SectionIndex == uint32(-1)) { if (Track) { Track->OnEndEvaluation(PersistentDataProxy, Player); } PersistentDataProxy.ResetTrackData(); } else if (Track && Track->HasChildTemplate(Key.SectionIndex)) { PersistentDataProxy.SetSectionKey(Key); Track->GetChildTemplate(Key.SectionIndex).OnEndEvaluation(PersistentDataProxy, Player); PersistentDataProxy.ResetSectionData(); } if (bStaleTrack && DelayedRestore) { DelayedRestore->Add(Key); } else if (TemplateMetaData) { TemplateMetaData->StopTrackingCaptureSource(Key, RootInstanceHandle); } } else if (TemplateMetaData) { // If the track has been destroyed since it was last evaluated, we can still restore state for anything it made // In particular this is needed for movie renders, where it will enable/disable shots between cuts in order // to render handle frames TemplateMetaData->StopTrackingCaptureSource(Key, RootInstanceHandle); } } for (const FMovieSceneOrderedEvaluationKey& OrderedKey : NewEntities) { FMovieSceneEvaluationKey Key = OrderedKey.Key; // Ensure we're able to find the sequence instance in our root if we've overridden Key.SequenceID = RootOverridePath.ResolveChildSequenceID(Key.SequenceID); const FCachedPtrs& EvalPtrs = CachedPtrs.FindChecked(Key.SequenceID); const FMovieSceneEvaluationTrack* Track = EvalPtrs.Template->FindTrack(Key.TrackIdentifier); if (Track) { PersistentDataProxy.SetTrackKey(Key.AsTrack()); if (Key.SectionIndex == uint32(-1)) { Track->OnBeginEvaluation(PersistentDataProxy, Player); } else { PersistentDataProxy.SetSectionKey(Key); Track->GetChildTemplate(Key.SectionIndex).OnBeginEvaluation(PersistentDataProxy, Player); } } } } void FMovieSceneTrackEvaluator::InvalidateCachedData() { CachedPtrs.Empty(); CachedReallocationVersion = 0; } void FMovieSceneTrackEvaluator::CopyActuators(FMovieSceneBlendingAccumulator& Accumulator) const { Accumulator.Actuators = ExecutionTokens.GetBlendingAccumulator().Actuators; } UMovieSceneSequence* FMovieSceneTrackEvaluator::GetSequence(FMovieSceneSequenceIDRef SequenceID) const { UMovieSceneSequence* RootSequencePtr = RootSequence.Get(); if (!RootSequencePtr) { return nullptr; } else if (SequenceID == MovieSceneSequenceID::Root) { return RootSequencePtr; } const FMovieSceneSequenceHierarchy* RootHierarchy = CompiledDataManager->FindHierarchy(RootCompiledDataID); const FMovieSceneSubSequenceData* SubData = RootHierarchy ? RootHierarchy->FindSubData(SequenceID) : nullptr; return SubData ? SubData->GetSequence() : nullptr; }