// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneTestObjects.h" #include "Misc/AutomationTest.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "Compilation/MovieSceneSegmentCompiler.h" #include "Compilation/MovieSceneCompilerRules.h" #include "Evaluation/MovieSceneEvaluationTrack.h" #include "Evaluation/MovieSceneEvaluationField.h" #include "Algo/Find.h" #include "UObject/Package.h" #include "MovieSceneTimeHelpers.h" // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TODO: Reimplement compiler automation tests // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UE_MOVIESCENE_TODO(Reimplement compiler automation tests) #if 0 #if WITH_DEV_AUTOMATION_TESTS IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMovieSceneCompilerPerfTest, "System.Engine.Sequencer.Compiler.Perf", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter | EAutomationTestFlags::Disabled) bool FMovieSceneCompilerPerfTest::RunTest(const FString& Parameters) { static bool bInvalidateEveryIteration = false; static int32 NumIterations = 1000000; FFrameRate TickResolution(1000, 1); UTestMovieSceneSequence* Sequence = NewObject(GetTransientPackage()); Sequence->MovieScene->SetTickResolutionDirectly(TickResolution); for (int32 i = 0; i < 100; ++i) { UTestMovieSceneTrack* Track = Sequence->MovieScene->AddTrack(); int32 NumSections = FMath::Rand() % 10; for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) { UTestMovieSceneSection* Section = NewObject(Track); double StartSeconds = FMath::FRand() * 60.f; double DurationSeconds = FMath::FRand() * 60.f; Section->SetRange(TRange::Inclusive( (StartSeconds * TickResolution).RoundToFrame(), ((StartSeconds + DurationSeconds) * TickResolution).RoundToFrame() )); Track->SectionArray.Add(Section); } } struct FTestMovieScenePlayer : IMovieScenePlayer { FMovieSceneRootEvaluationTemplateInstance RootInstance; virtual FMovieSceneRootEvaluationTemplateInstance& GetEvaluationTemplate() override { return RootInstance; } virtual void SetViewportSettings(const TMap& ViewportParamsMap) override {} virtual void GetViewportSettings(TMap& ViewportParamsMap) const override {} virtual EMovieScenePlayerStatus::Type GetPlaybackStatus() const override { return EMovieScenePlayerStatus::Playing; } virtual void SetPlaybackStatus(EMovieScenePlayerStatus::Type InPlaybackStatus) override {} } TestPlayer; UMovieSceneCompiledDataManager* CompiledDataManager = UMovieSceneCompiledDataManager::GetPrecompiledData(); FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(Sequence); TestPlayer.RootInstance.Initialize(*Sequence, TestPlayer); for (int32 i = 0; i < NumIterations; ++i) { if (bInvalidateEveryIteration) { CompiledDataManager->RemoveTrackTemplateField(DataID); } double StartSeconds = FMath::FRand() * 60.f; double DurationSeconds = FMath::FRand() * 1.f; FMovieSceneEvaluationRange EvaluatedRange(TRange(StartSeconds * TickResolution, (StartSeconds + DurationSeconds) * TickResolution), TickResolution, EPlayDirection::Forwards); TestPlayer.RootInstance.Evaluate(EvaluatedRange, TestPlayer); } return true; } TRange MakeRange(FFrameNumber LowerBound, FFrameNumber UpperBound, ERangeBoundTypes::Type LowerType, ERangeBoundTypes::Type UpperType) { return LowerType == ERangeBoundTypes::Inclusive ? UpperType == ERangeBoundTypes::Inclusive ? TRange(TRangeBound::Inclusive(LowerBound), TRangeBound::Inclusive(UpperBound)) : TRange(TRangeBound::Inclusive(LowerBound), TRangeBound::Exclusive(UpperBound)) : UpperType == ERangeBoundTypes::Inclusive ? TRange(TRangeBound::Exclusive(LowerBound), TRangeBound::Inclusive(UpperBound)) : TRange(TRangeBound::Exclusive(LowerBound), TRangeBound::Exclusive(UpperBound)); } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMovieSceneCompilerEvaluationRangeTest, "System.Engine.Sequencer.Compiler.EvaluationRange", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FMovieSceneCompilerEvaluationRangeTest::RunTest(const FString& Parameters) { double StartSecondTimes [] = { 400.0f, 400.0f, 400.0f, 400.0f, 400.1f, 400.5f, 400.8f }; double DurationSecondTimes[] = { 400.0f, 400.1f, 400.5f, 400.8f, 400.0f, 400.0f, 400.0f }; FFrameRate TickResolution(1, 1); for (int32 Index = 0; Index < 7; ++Index) { double StartSeconds = StartSecondTimes[Index]; double DurationSeconds = DurationSecondTimes[Index]; FFrameTime LowerBound = StartSeconds * TickResolution; FFrameTime UpperBound = (StartSeconds + DurationSeconds) * TickResolution; FMovieSceneEvaluationRange EvaluatedRange(TRange(LowerBound, UpperBound), TickResolution, EPlayDirection::Forwards); TRange TimeRange = EvaluatedRange.GetRange(); UTEST_EQUAL("Range - Lower Bound", TimeRange.GetLowerBoundValue(), LowerBound); UTEST_EQUAL("Range - Upper Bound", TimeRange.GetUpperBoundValue(), UpperBound); TRange FrameNumberRange = EvaluatedRange.GetFrameNumberRange(); // Subframe evaluation range tests // For example, a range of [400.5, 800.5[ will return a lower bound frame of 401 since the frame is already beyond 400 UTEST_EQUAL("FrameNumberRange - Lower Bound Frame", FrameNumberRange.GetLowerBoundValue().Value, FMath::CeilToInt(StartSeconds)); // For example, a range of [400, 800.5[ will return an upper bound frame of 801 so that a key at 800 will be evaluated UTEST_EQUAL("FrameNumberRange - Upper Bound Frame", FrameNumberRange.GetUpperBoundValue().Value, FMath::CeilToInt(StartSeconds + DurationSeconds)); } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMovieSceneCompilerRangeTest, "System.Engine.Sequencer.Compiler.Ranges", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FMovieSceneCompilerRangeTest::RunTest(const FString& Parameters) { FFrameNumber CompileAtTimes[] = { -3, -2, -1, 0, 1, 2, 3, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }; // Test each combination of inc/excl boundary conditions for adjacent and adjoining ranges TRange Ranges[] = { MakeRange(-2, -1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange(-2, -1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange(-2, -1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange(-2, -1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange(-1, -1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange(-1, -1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange(-1, -1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange(-1, -1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange(-1, 0, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange(-1, 0, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange(-1, 0, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange(-1, 0, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 0, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 0, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 0, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 0, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange( 1, 1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 1, 1, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange( 1, 1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 1, 1, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 2, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 2, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange( 0, 2, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 0, 2, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Exclusive), MakeRange( 10, 15, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 9, 15, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 10, 15, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 11, 15, ERangeBoundTypes::Exclusive, ERangeBoundTypes::Inclusive), MakeRange( 13, 17, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 13, 18, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Exclusive), MakeRange( 13, 19, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 13, 18, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), // Explicitly test two adjacent ranges that would produce effectively empty space in between them when iterating MakeRange( 21, 22, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), MakeRange( 23, 24, ERangeBoundTypes::Inclusive, ERangeBoundTypes::Inclusive), }; UTestMovieSceneSequence* Sequence = NewObject(GetTransientPackage()); for (TRange Range : Ranges) { UTestMovieSceneTrack* Track = Sequence->MovieScene->AddTrack(); UTestMovieSceneSection* Section = NewObject(Track); Section->SetRange(Range); Track->SectionArray.Add(Section); } struct FTemplateStore : IMovieSceneSequenceTemplateStore { FMovieSceneEvaluationTemplate& AccessTemplate(UMovieSceneSequence&) override { return Template; } FMovieSceneEvaluationTemplate Template; } Store; // Compile individual times for (FFrameNumber Time : CompileAtTimes) { FMovieSceneCompiler::CompileRange(TRange::Inclusive(Time, Time), *Sequence, Store); } // Compile a whole range Store.Template = FMovieSceneEvaluationTemplate(); FMovieSceneCompiler::CompileRange(TRange::All(), *Sequence, Store); // Compile the whole sequence Store.Template = FMovieSceneEvaluationTemplate(); FMovieSceneCompiler::Compile(*Sequence, Store); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMovieSceneCompilerEmptySpaceOnTheFlyTest, "System.Engine.Sequencer.Compiler.Empty Space On The Fly", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FMovieSceneCompilerEmptySpaceOnTheFlyTest::RunTest(const FString& Parameters) { // Tests that compiling ranges that contain empty space works correctly by verifying that the result evaluation field entries are either populated or empty as expected struct FResult { TRange FieldRange; bool bExpectEmpty; }; struct FTest { TArray> CompileRanges; TArray ExpectedResults; }; TRange SectionRanges[] = { TRange(0, 10), TRange(20, 30), TRange(40, 50), TRange(60, 70), }; FResult ExpectedResults[] = { { TRange(0, 10), false }, { TRange(10, 20), true }, { TRange(20, 30), false }, { TRange(30, 40), true }, { TRange(40, 50), false }, { TRange(50, 60), true }, { TRange(60, 70), false }, }; TArray Tests; // Test 0: Test that compiling a range that only overlaps a section results in only that section's time being compiled { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(5, 6) }; Tests.Last().ExpectedResults = { ExpectedResults[0] }; } // Test 1: Test that compiling a range that overlaps both a section and empty space results in an entry for the section and the empty space { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(6, 15) }; Tests.Last().ExpectedResults = { ExpectedResults[0], ExpectedResults[1] }; } // Test 2: Test that compiling a range that only overlaps empty space works as expected { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(14, 15) }; Tests.Last().ExpectedResults = { ExpectedResults[1] }; } // Test 3: Test that compiling a section range followed by a range that overlaps both that section, and subsequent empty space compiles the empty space correctly { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(5, 6), TRange(6, 15) }; Tests.Last().ExpectedResults = { ExpectedResults[0], ExpectedResults[1] }; } // Test 4: Test that compiling section range followed by a range that overlaps empty space preceeding that section and the section itself, compiles correctly (reverse of Test3) { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(24, 25), TRange(15, 24), TRange(5, 6) }; Tests.Last().ExpectedResults = { ExpectedResults[0], ExpectedResults[1], ExpectedResults[2] }; } // Test 5: Test that compiling a range encomassing the entire track results in the correct field ranges { Tests.Emplace(); Tests.Last().CompileRanges = { TRange(0, 70) }; Tests.Last().ExpectedResults = TArray(&ExpectedResults[0], UE_ARRAY_COUNT(ExpectedResults)); } UTestMovieSceneSequence* Sequence = NewObject(GetTransientPackage()); UTestMovieSceneTrack* Track = Sequence->MovieScene->AddTrack(); for (TRange Range : SectionRanges) { UTestMovieSceneSection* Section = NewObject(Track); Section->SetRange(Range); Track->SectionArray.Add(Section); } UMovieSceneCompiledDataManager* CompileDataManager = NewObject(GetTransientPackage()); struct FTemplateStore : IMovieSceneSequenceTemplateStore { FMovieSceneEvaluationTemplate& AccessTemplate(UMovieSceneSequence&) override { return Template; } FMovieSceneEvaluationTemplate Template; } Store; for (int32 Index = 0; Index < Tests.Num(); ++Index) { const FTest& Test = Tests[Index]; // Wipe the evaluation template before each test Store.Template = FMovieSceneEvaluationTemplate(); // Compile all the ranges that the test demands for (TRange CompileRange : Test.CompileRanges) { FMovieSceneCompiler::CompileRange(CompileRange, *Sequence, Store); } // Verify that the resulting evaluation field is what we expect TArrayView FieldRanges = Store.Template.EvaluationField.GetRanges(); if (!FieldRanges.Num()) { AddError(FString::Printf(TEXT("Test index %02d: No evaluation field entries were compiled."), Index)); continue; } for (const FResult& Result : Test.ExpectedResults) { // Find the field entry that exactly matches our expected result const FMovieSceneFrameRange* FieldRange = Algo::FindBy(FieldRanges, Result.FieldRange, &FMovieSceneFrameRange::Value); if (!FieldRange) { AddError(FString::Printf(TEXT("Test index %02d: Expected to find an evaluation field entry for range %s but did not."), Index, *LexToString(Result.FieldRange))); } else { // Verify that the field entry is either empty or populated as the test expects const int32 FieldIndex = FieldRange - FieldRanges.GetData(); const bool FieldIsEmptyHere = (Store.Template.EvaluationField.GetGroup(FieldIndex).TrackLUT.Num() == 0); if (Result.bExpectEmpty != FieldIsEmptyHere) { const TCHAR* ExpectedString = Result.bExpectEmpty ? TEXT("empty") : TEXT("populated"); const TCHAR* ActualString = FieldIsEmptyHere ? TEXT("populated") : TEXT("empty"); AddError(FString::Printf(TEXT("Test index %02d: Expected evaluation field entry range %s to be %s but it was %s."), Index, ExpectedString, *LexToString(Result.FieldRange), ActualString)); } } } } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMovieSceneCompilerSubSequencesTest, "System.Engine.Sequencer.Compiler.SubSequences", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FMovieSceneCompilerSubSequencesTest::RunTest(const FString& Parameters) { struct FTest { TArray> CompileRanges; }; struct FTemplateStore : public IMovieSceneSequenceTemplateStore { FTemplateStore(const TArray& InExpectedSequences) : ExpectedSequences(InExpectedSequences) { Templates.SetNum(InExpectedSequences.Num()); } FMovieSceneEvaluationTemplate& AccessTemplate(UMovieSceneSequence& InSequence) override { for (int32 i = 0; i < ExpectedSequences.Num(); ++i) { if (ExpectedSequences[i] == &InSequence) { return Templates[i]; } } check(false); return Templates[0]; } private: TArray ExpectedSequences; TArray Templates; }; UTestMovieSceneSequence* RootSequence = NewObject(GetTransientPackage()); UTestMovieSceneSubTrack* RootSubTrack = RootSequence->MovieScene->AddTrack(); UTestMovieSceneSequence* Shot1Sequence = NewObject(GetTransientPackage()); Shot1Sequence->GetMovieScene()->SetPlaybackRange(0, 100); UTestMovieSceneSubSection* Shot1SubSection = NewObject(RootSubTrack); Shot1SubSection->SetRange(TRange(0, 100)); Shot1SubSection->SetSequence(Shot1Sequence); RootSubTrack->SectionArray.Add(Shot1SubSection); UTestMovieSceneTrack* Shot1Track = Shot1Sequence->MovieScene->AddTrack(); UTestMovieSceneSection* Shot1Section = NewObject(Shot1Track); Shot1Section->SetRange(TRange(0, 60)); Shot1Track->SectionArray.Add(Shot1Section); FMovieSceneSequenceID Shot1SequenceID = Shot1SubSection->GetSequenceID(); { FTemplateStore Store({ RootSequence, Shot1Sequence }); FMovieSceneCompiler::Compile(*RootSequence, Store); const FMovieSceneEvaluationTemplate& RootTemplate = Store.AccessTemplate(*RootSequence); UTEST_EQUAL("Ranges count", RootTemplate.EvaluationField.GetRanges().Num(), 3); UTEST_EQUAL("First range", RootTemplate.EvaluationField.GetRange(1), TRange(0, 60)); const FMovieSceneEvaluationGroup& EvalGroup = RootTemplate.EvaluationField.GetGroup(1); UTEST_EQUAL("Sequence ID", EvalGroup.SegmentPtrLUT[0].SequenceID, Shot1SequenceID); } return true; } #endif // WITH_DEV_AUTOMATION_TESTS #endif // #if 0