Files
UnrealEngineUWP/Engine/Source/Runtime/MovieScene/Private/EntitySystem/MovieSceneEvaluationHookSystem.cpp
max chen a20dc348ee Sequencer: Pre-Animated state changes to support track instance inputs
- Track Instance Inputs can now be used as capture sources to capture pre-animated state
  - PreAnimatedState extension now simply exists as a member of UMovieSceneEntitySystem. Previously the existence of the extension was used to determine whether any pre-animated state logic was required, but in practice this created more problems than it solved and led to some convoluted lifetime management code.
  - Added a templated group manager type from which the object group manager can inherit. This simplifies the introduction of new group managers
  - Removed the requirement that all pre-animated state must exist as part of a valid group. This forced state from 'master' tracks to be arbitrarily grouped together which put an unnecessary burden on the storage classes.

#jira UE-132512
#rb max.chen, matt.hoffman
[FYI] andrew.rodham
#preflight 61f2ca893765218cf01f290e

#ROBOMERGE-AUTHOR: max.chen
#ROBOMERGE-SOURCE: CL 18754810 in //UE5/Release-5.0/... via CL 18754825 via CL 18757520
#ROBOMERGE-BOT: UE5 (Release-Engine-Test -> Main) (v903-18687472)

[CL 18758489 by max chen in ue5-main branch]
2022-01-27 14:35:51 -05:00

296 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EntitySystem/MovieSceneEvaluationHookSystem.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieSceneEntitySystemRunner.h"
#include "EntitySystem/MovieSceneSpawnablesSystem.h"
#include "EntitySystem/MovieSceneEntitySystemTask.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedCaptureSource.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "IMovieScenePlayer.h"
DECLARE_CYCLE_STAT(TEXT("Generic Hooks"), MovieSceneECS_GenericHooks, STATGROUP_MovieSceneECS);
namespace UE
{
namespace MovieScene
{
struct FEvaluationHookUpdater
{
UMovieSceneEvaluationHookSystem* HookSystem;
FInstanceRegistry* InstanceRegistry;
FEvaluationHookUpdater(UMovieSceneEvaluationHookSystem* InHookSystem, FInstanceRegistry* InInstanceRegistry)
: HookSystem(InHookSystem), InstanceRegistry(InInstanceRegistry)
{}
void ForEachAllocation(FEntityAllocationProxy Item, TRead<FInstanceHandle> InstanceHandles, TRead<FMovieSceneEvaluationHookComponent> Hooks, TRead<FFrameTime> EvalTimes, TWrite<FEvaluationHookFlags> WriteFlags)
{
const int32 Num = Item.GetAllocation()->Num();
const bool bRestoreState = Item.GetAllocationType().Contains(FBuiltInComponentTypes::Get()->Tags.RestoreState);
for (int32 Index = 0; Index < Num; ++Index)
{
if (WriteFlags[Index].bHasBegun == false)
{
WriteFlags[Index].bHasBegun = true;
continue;
}
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(InstanceHandles[Index]);
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::Update;
NewEvent.RootTime = EvalTimes[Index] * SequenceInstance.GetContext().GetSequenceToRootTransform();
NewEvent.SequenceID = SequenceInstance.GetSequenceID();
NewEvent.bRestoreState = bRestoreState;
HookSystem->AddEvent(SequenceInstance.GetRootInstanceHandle(), NewEvent);
}
}
};
struct FEvaluationHookSorter
{
UMovieSceneEvaluationHookSystem* HookSystem;
FEvaluationHookSorter(UMovieSceneEvaluationHookSystem* InHookSystem)
: HookSystem(InHookSystem)
{}
FORCEINLINE TStatId GetStatId() const { return GET_STATID(MovieSceneECS_GenericHooks); }
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyHiPriThreadHiPriTask; }
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
Run();
}
void Run()
{
HookSystem->SortEvents();
}
};
} // namespace MovieScene
} // namespace UE
UMovieSceneEvaluationHookSystem::UMovieSceneEvaluationHookSystem(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
Phase = UE::MovieScene::ESystemPhase::Instantiation | UE::MovieScene::ESystemPhase::Evaluation | UE::MovieScene::ESystemPhase::Finalization;
if (HasAnyFlags(RF_ClassDefaultObject))
{
DefineComponentConsumer(GetClass(), UE::MovieScene::FBuiltInComponentTypes::Get()->EvalTime);
}
}
void UMovieSceneEvaluationHookSystem::AddEvent(UE::MovieScene::FInstanceHandle RootInstance, const FMovieSceneEvaluationHookEvent& InEvent)
{
PendingEventsByRootInstance.FindOrAdd(RootInstance).Events.Add(InEvent);
}
bool UMovieSceneEvaluationHookSystem::HasEvents() const
{
return PendingEventsByRootInstance.Num() != 0;
}
bool UMovieSceneEvaluationHookSystem::IsRelevantImpl(UMovieSceneEntitySystemLinker* InLinker) const
{
return HasEvents() || InLinker->EntityManager.ContainsComponent(UE::MovieScene::FBuiltInComponentTypes::Get()->EvaluationHook);
}
void UMovieSceneEvaluationHookSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
FMovieSceneEntitySystemRunner* Runner = Linker->GetActiveRunner();
if (!ensure(Runner))
{
return;
}
ESystemPhase CurrentPhase = Runner->GetCurrentPhase();
if (CurrentPhase == ESystemPhase::Instantiation)
{
UpdateHooks();
}
else if (CurrentPhase == ESystemPhase::Evaluation)
{
FBuiltInComponentTypes* Components = FBuiltInComponentTypes::Get();
FGraphEventRef UpdateEvent = FEntityTaskBuilder()
.Read(Components->InstanceHandle)
.Read(Components->EvaluationHook)
.Read(Components->EvalTime)
.Write(Components->EvaluationHookFlags)
.Dispatch_PerAllocation<FEvaluationHookUpdater>(&Linker->EntityManager, InPrerequisites, &Subsequents, this, Linker->GetInstanceRegistry());
if (Linker->EntityManager.GetThreadingModel() == EEntityThreadingModel::NoThreading)
{
this->SortEvents();
}
else
{
// The only thing we depend on is the gather task
FGraphEventArray Prereqs = { UpdateEvent };
FGraphEventRef SortTask = TGraphTask<FEvaluationHookSorter>::CreateTask(&Prereqs, Linker->EntityManager.GetDispatchThread())
.ConstructAndDispatchWhenReady(this);
Subsequents.AddMasterTask(SortTask);
}
}
else if (HasEvents())
{
ensure(CurrentPhase == ESystemPhase::Finalization);
Runner->GetQueuedEventTriggers().AddUObject(this, &UMovieSceneEvaluationHookSystem::TriggerAllEvents);
}
}
void UMovieSceneEvaluationHookSystem::UpdateHooks()
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* Components = FBuiltInComponentTypes::Get();
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
auto VisitNew = [this, InstanceRegistry](FEntityAllocationProxy Item, TRead<FInstanceHandle> InstanceHandles, TRead<FFrameTime> EvalTimes, TRead<FMovieSceneEvaluationHookComponent> Hooks)
{
const int32 Num = Item.GetAllocation()->Num();
const bool bRestoreState = Item.GetAllocationType().Contains(FBuiltInComponentTypes::Get()->Tags.RestoreState);
for (int32 Index = 0; Index < Num; ++Index)
{
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(InstanceHandles[Index]);
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::Begin;
NewEvent.RootTime = EvalTimes[Index] * SequenceInstance.GetContext().GetSequenceToRootTransform();
NewEvent.SequenceID = SequenceInstance.GetSequenceID();
NewEvent.bRestoreState = bRestoreState;
this->AddEvent(SequenceInstance.GetRootInstanceHandle(), NewEvent);
}
};
auto VisitOld = [this, InstanceRegistry](FEntityAllocationProxy Item, TRead<FInstanceHandle> InstanceHandles, TRead<FFrameTime> EvalTimes, TRead<FMovieSceneEvaluationHookComponent> Hooks)
{
const int32 Num = Item.GetAllocation()->Num();
const bool bRestoreState = Item.GetAllocationType().Contains(FBuiltInComponentTypes::Get()->Tags.RestoreState);
for (int32 Index = 0; Index < Num; ++Index)
{
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(InstanceHandles[Index]);
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::End;
NewEvent.RootTime = EvalTimes[Index] * SequenceInstance.GetContext().GetSequenceToRootTransform();
NewEvent.SequenceID = SequenceInstance.GetSequenceID();
NewEvent.bRestoreState = bRestoreState;
this->AddEvent(SequenceInstance.GetRootInstanceHandle(), NewEvent);
}
};
FEntityTaskBuilder()
.Read(Components->InstanceHandle)
.Read(Components->EvalTime)
.Read(Components->EvaluationHook)
.FilterAny({ Components->Tags.NeedsLink })
.Iterate_PerAllocation(&Linker->EntityManager, VisitNew);
FEntityTaskBuilder()
.Read(Components->InstanceHandle)
.Read(Components->EvalTime)
.Read(Components->EvaluationHook)
.FilterAny({ Components->Tags.Finished })
.Iterate_PerAllocation(&Linker->EntityManager, VisitOld);
}
void UMovieSceneEvaluationHookSystem::SortEvents()
{
using namespace UE::MovieScene;
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
for (TPair<FMovieSceneEvaluationInstanceKey, FMovieSceneEvaluationHookEventContainer>& Pair : PendingEventsByRootInstance)
{
const FSequenceInstance& RootInstance = InstanceRegistry->GetInstance(Pair.Key.InstanceHandle);
if (RootInstance.GetContext().GetDirection() == EPlayDirection::Forwards)
{
Algo::SortBy(Pair.Value.Events, &FMovieSceneEvaluationHookEvent::RootTime);
}
else
{
Algo::SortBy(Pair.Value.Events, &FMovieSceneEvaluationHookEvent::RootTime, TGreater<>());
}
}
}
void UMovieSceneEvaluationHookSystem::TriggerAllEvents()
{
using namespace UE::MovieScene;
SCOPE_CYCLE_COUNTER(MovieSceneECS_GenericHooks);
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
// We need to clean our state before actually triggering the events because one of those events could
// call back into an evaluation (for instance, by starting play on another sequence). If we don't clean
// this before, would would re-enter and re-trigger past events, resulting in an infinite loop!
TMap<FMovieSceneEvaluationInstanceKey, FMovieSceneEvaluationHookEventContainer> LocalEvents;
Swap(LocalEvents, PendingEventsByRootInstance);
for (TPair<FMovieSceneEvaluationInstanceKey, FMovieSceneEvaluationHookEventContainer>& Pair : LocalEvents)
{
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(Pair.Key.InstanceHandle);
IMovieScenePlayer* Player = SequenceInstance.GetPlayer();
FMovieSceneContext RootContext = SequenceInstance.GetContext();
for (const FMovieSceneEvaluationHookEvent& Event : Pair.Value.Events)
{
FScopedPreAnimatedCaptureSource CaptureSource(&Player->PreAnimatedState, Event.Hook.Interface.GetObject(), Event.SequenceID, Event.bRestoreState);
FEvaluationHookParams Params = {
Event.Hook.ObjectBindingID, RootContext, Event.SequenceID, Event.TriggerIndex
};
if (Event.SequenceID != MovieSceneSequenceID::Root)
{
FInstanceHandle SubInstance = SequenceInstance.FindSubInstance(Event.SequenceID);
if (SubInstance.IsValid())
{
Params.Context = InstanceRegistry->GetInstance(SubInstance).GetContext();
}
}
switch (Event.Type)
{
case EEvaluationHookEvent::Begin:
Event.Hook.Interface->Begin(Player, Params);
break;
case EEvaluationHookEvent::Update:
Event.Hook.Interface->Update(Player, Params);
break;
case EEvaluationHookEvent::End:
Event.Hook.Interface->End(Player, Params);
Player->PreAnimatedState.OnFinishedEvaluating(Event.Hook.Interface.GetObject(), Event.SequenceID);
break;
case EEvaluationHookEvent::Trigger:
Event.Hook.Interface->Trigger(Player, Params);
break;
}
}
}
}