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

294 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EntitySystem/MovieSceneEvaluationHookSystem.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieSceneEntitySystemRunner.h"
#include "EntitySystem/MovieSceneEntitySystemTask.h"
#include "EntitySystem/MovieSceneSharedPlaybackState.h"
#include "EntitySystem/MovieSceneSpawnablesSystem.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedCaptureSource.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedCaptureSources.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneEvaluationHookSystem)
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
{
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]);
FMovieSceneInverseSequenceTransform SequenceToRootTransform = SequenceInstance.GetContext().GetRootToSequenceTransform().Inverse();
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::Update;
NewEvent.RootTime = SequenceToRootTransform.TryTransformTime(EvalTimes[Index], SequenceInstance.GetContext().GetRootToSequenceWarpCounter()).Get(FFrameTime());
NewEvent.RootInstanceHandle = SequenceInstance.GetRootInstanceHandle();
NewEvent.SequenceID = SequenceInstance.GetSequenceID();
NewEvent.bRestoreState = bRestoreState;
HookSystem->AddEvent(SequenceInstance.GetRootInstanceHandle(), NewEvent);
}
}
void PostTask()
{
HookSystem->SortEvents();
}
};
} // namespace MovieScene
} // namespace UE
UMovieSceneEvaluationHookSystem::UMovieSceneEvaluationHookSystem(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
Phase = UE::MovieScene::ESystemPhase::Instantiation | UE::MovieScene::ESystemPhase::Scheduling | 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::OnSchedulePersistentTasks(UE::MovieScene::IEntitySystemScheduler* TaskScheduler)
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* Components = FBuiltInComponentTypes::Get();
FEntityTaskBuilder()
.Read(Components->InstanceHandle)
.Read(Components->EvaluationHook)
.Read(Components->EvalTime)
.Write(Components->EvaluationHookFlags)
.SetDesiredThread(Linker->EntityManager.GetDispatchThread())
.Schedule_PerAllocation<FEvaluationHookUpdater>(&Linker->EntityManager, TaskScheduler, this, Linker->GetInstanceRegistry());
}
void UMovieSceneEvaluationHookSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
TSharedRef<FMovieSceneEntitySystemRunner> Runner = Linker->GetRunner();
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)
.SetDesiredThread(Linker->EntityManager.GetDispatchThread())
.Dispatch_PerAllocation<FEvaluationHookUpdater>(&Linker->EntityManager, InPrerequisites, &Subsequents, this, Linker->GetInstanceRegistry());
}
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]);
FMovieSceneInverseSequenceTransform SequenceToRootTransform = SequenceInstance.GetContext().GetRootToSequenceTransform().Inverse();
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::Begin;
NewEvent.RootTime = SequenceToRootTransform.TryTransformTime(EvalTimes[Index], SequenceInstance.GetContext().GetRootToSequenceWarpCounter()).Get(FFrameTime());
NewEvent.RootInstanceHandle = SequenceInstance.GetRootInstanceHandle();
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]);
FMovieSceneInverseSequenceTransform SequenceToRootTransform = SequenceInstance.GetContext().GetRootToSequenceTransform().Inverse();
FMovieSceneEvaluationHookEvent NewEvent;
NewEvent.Hook = Hooks[Index];
NewEvent.Type = EEvaluationHookEvent::End;
NewEvent.RootTime = SequenceToRootTransform.TryTransformTime(EvalTimes[Index], SequenceInstance.GetContext().GetRootToSequenceWarpCounter()).Get(FFrameTime());
NewEvent.RootInstanceHandle = SequenceInstance.GetRootInstanceHandle();
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();
FPreAnimatedEvaluationHookCaptureSources* EvaluationHookMetaData = Linker->PreAnimatedState.GetEvaluationHookMetaData();
// 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)
{
for (const FMovieSceneEvaluationHookEvent& Event : Pair.Value.Events)
{
// Re-fetch the sequence instance in each loop. This is because the hook we execute might run
// arbitrary code that, for instance, starts other sequences. In that case, the instance registry
// would grow, and rellocate all sequence instances. If we held onto one of them, we could
// end up holding deallocated memory.
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(Pair.Key.InstanceHandle);
FMovieSceneContext RootContext = SequenceInstance.GetContext();
TSharedRef<FSharedPlaybackState> SharedPlaybackState = SequenceInstance.GetSharedPlaybackState();
FScopedPreAnimatedCaptureSource CaptureSource(SharedPlaybackState, 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(SharedPlaybackState, Params);
break;
case EEvaluationHookEvent::Update:
Event.Hook.Interface->Update(SharedPlaybackState, Params);
break;
case EEvaluationHookEvent::End:
Event.Hook.Interface->End(SharedPlaybackState, Params);
if (EvaluationHookMetaData)
{
EvaluationHookMetaData->StopTrackingCaptureSource(Event.Hook.Interface.GetObject(), Event.RootInstanceHandle, Event.SequenceID);
}
break;
case EEvaluationHookEvent::Trigger:
Event.Hook.Interface->Trigger(SharedPlaybackState, Params);
break;
}
}
}
}