1350 lines
49 KiB
C++
1350 lines
49 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "EntitySystem/MovieSceneEntitySystemRunner.h"
|
|
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
|
|
#include "EntitySystem/MovieSceneEntityMutations.h"
|
|
#include "EntitySystem/BuiltInComponentTypes.h"
|
|
#include "EntitySystem/MovieSceneEntitySystemTypes.h"
|
|
#include "EntitySystem/MovieSceneTaskScheduler.h"
|
|
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedCaptureSource.h"
|
|
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
|
|
#include "IMovieScenePlayer.h"
|
|
#include "MovieSceneSequence.h"
|
|
#include "Algo/Reverse.h"
|
|
#include "Algo/Sort.h"
|
|
#include "ProfilingDebugging/CountersTrace.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Runner Flush"), MovieSceneEval_RunnerFlush, STATGROUP_MovieSceneEval);
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Spawn Phase"), MovieSceneEval_SpawnPhase, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Post Spawn Event"), MovieSceneEval_PostSpawnEvent, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Instantiation Phase"), MovieSceneEval_InstantiationPhase, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Instantiation Async Tasks"), MovieSceneEval_AsyncInstantiationTasks, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Post Instantiation"), MovieSceneEval_PostInstantiation, STATGROUP_MovieSceneECS);
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Evaluation Phase"), MovieSceneEval_EvaluationPhase, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Finalization Phase"), MovieSceneEval_FinalizationPhase, STATGROUP_MovieSceneECS);
|
|
DECLARE_CYCLE_STAT(TEXT("Post Evaluation Phase"), MovieSceneEval_PostEvaluationPhase, STATGROUP_MovieSceneECS);
|
|
|
|
namespace UE::MovieScene::FlushState
|
|
{
|
|
// Signifies that, during the Finalization task, there were still outstanding tasks and we need to perform another iteration
|
|
ERunnerFlushState LoopEval = ERunnerFlushState::ConditionalRecompile
|
|
| ERunnerFlushState::Import
|
|
| ERunnerFlushState::Spawn
|
|
| ERunnerFlushState::Instantiation
|
|
| ERunnerFlushState::Evaluation
|
|
| ERunnerFlushState::Finalization
|
|
| ERunnerFlushState::EventTriggers
|
|
| ERunnerFlushState::PostEvaluation
|
|
| ERunnerFlushState::End;
|
|
|
|
ERunnerFlushState Everything = ERunnerFlushState::Start | LoopEval;
|
|
}
|
|
|
|
|
|
/**
|
|
* Structure for making it possible to make re-entrant evaluation on a linker.
|
|
*/
|
|
struct FMovieSceneEntitySystemEvaluationReentrancyWindow
|
|
{
|
|
FMovieSceneEntitySystemRunner* Runner;
|
|
UMovieSceneEntitySystemLinker* Linker;
|
|
int32 ReentrancyLevel;
|
|
UE::MovieScene::ERunnerFlushState CachedFlushState;
|
|
UE::MovieScene::ERunnerFlushState CachedCurrentFlushState;
|
|
|
|
FMovieSceneEntitySystemEvaluationReentrancyWindow(FMovieSceneEntitySystemRunner* InRunner, UMovieSceneEntitySystemLinker* InLinker)
|
|
: Runner(InRunner)
|
|
, Linker(InLinker)
|
|
, ReentrancyLevel(Linker->RunnerReentrancyFlags.Num() - 1)
|
|
, CachedFlushState(Runner->FlushState)
|
|
, CachedCurrentFlushState(Runner->CurrentFlushState)
|
|
{
|
|
check(ReentrancyLevel >= 0);
|
|
checkf(Linker->RunnerReentrancyFlags[ReentrancyLevel] == false, TEXT("Nested FMovieSceneEntitySystemEvaluationReentrancyWindows are not supported for the same active runner"));
|
|
|
|
Linker->RunnerReentrancyFlags[ReentrancyLevel] = true;
|
|
|
|
// Clear the current flush state to prevent inner scopes from flushing
|
|
// our pending states and set the flag that allows us to be re-entrant
|
|
Runner->FlushState = UE::MovieScene::ERunnerFlushState::None;
|
|
Runner->CurrentFlushState = UE::MovieScene::ERunnerFlushState::None;
|
|
}
|
|
|
|
~FMovieSceneEntitySystemEvaluationReentrancyWindow()
|
|
{
|
|
// If a re-entrant call left some evaluation still outstanding, we have to flush that before we can continue
|
|
if (Runner->FlushState != UE::MovieScene::ERunnerFlushState::None)
|
|
{
|
|
Runner->FlushOutstanding();
|
|
}
|
|
|
|
Runner->FlushState = CachedFlushState;
|
|
Runner->CurrentFlushState = CachedCurrentFlushState;
|
|
if (ensure(Linker->RunnerReentrancyFlags.IsValidIndex(ReentrancyLevel)))
|
|
{
|
|
Linker->RunnerReentrancyFlags[ReentrancyLevel] = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
FMovieSceneEntitySystemRunner::FMovieSceneEntitySystemRunner()
|
|
: GameThread(ENamedThreads::GameThread_Local)
|
|
, CurrentPhase(UE::MovieScene::ESystemPhase::None)
|
|
, FlushState(UE::MovieScene::ERunnerFlushState::None)
|
|
, CurrentFlushState(UE::MovieScene::ERunnerFlushState::None)
|
|
, bRequireFullFlush(false)
|
|
, bIsUpdatingSequence(false)
|
|
{
|
|
}
|
|
|
|
FMovieSceneEntitySystemRunner::FMovieSceneEntitySystemRunner(UMovieSceneEntitySystemLinker* InLinker)
|
|
: WeakLinker(InLinker)
|
|
, GameThread(ENamedThreads::GameThread_Local)
|
|
, CurrentPhase(UE::MovieScene::ESystemPhase::None)
|
|
, FlushState(UE::MovieScene::ERunnerFlushState::None)
|
|
, CurrentFlushState(UE::MovieScene::ERunnerFlushState::None)
|
|
, bRequireFullFlush(false)
|
|
, bIsUpdatingSequence(false)
|
|
{
|
|
}
|
|
|
|
FMovieSceneEntitySystemRunner::~FMovieSceneEntitySystemRunner()
|
|
{
|
|
}
|
|
|
|
UE::MovieScene::FEntityManager* FMovieSceneEntitySystemRunner::GetEntityManager() const
|
|
{
|
|
if (UMovieSceneEntitySystemLinker* Linker = GetLinker())
|
|
{
|
|
return &Linker->EntityManager;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UE::MovieScene::FInstanceRegistry* FMovieSceneEntitySystemRunner::GetInstanceRegistry() const
|
|
{
|
|
if (UMovieSceneEntitySystemLinker* Linker = GetLinker())
|
|
{
|
|
return Linker->GetInstanceRegistry();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::IsCurrentlyEvaluating() const
|
|
{
|
|
return FlushState != UE::MovieScene::ERunnerFlushState::None;
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::IsUpdatingSequence() const
|
|
{
|
|
return bIsUpdatingSequence;
|
|
}
|
|
|
|
int32 FMovieSceneEntitySystemRunner::GetQueuedUpdateCount() const
|
|
{
|
|
return UpdateQueue.Num() + DissectedUpdates.Num();
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::HasQueuedUpdates() const
|
|
{
|
|
if (UpdateQueue.Num() != 0 || DissectedUpdates.Num() != 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (const UMovieSceneEntitySystemLinker* Linker = GetLinker())
|
|
{
|
|
return Linker->HasStructureChangedSinceLastRun();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::HasQueuedUpdates(FInstanceHandle InInstanceHandle) const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
return Algo::FindBy(UpdateQueue, InInstanceHandle, [](const FUpdateParamsAndContext& In){ return In.Params.InstanceHandle; }) != nullptr ||
|
|
Algo::FindBy(DissectedUpdates, InInstanceHandle, &FDissectedUpdate::InstanceHandle) != nullptr;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::QueueUpdate(const FMovieSceneContext& Context, FInstanceHandle Instance, UE::MovieScene::ERunnerUpdateFlags Flags)
|
|
{
|
|
if (EnumHasAnyFlags(Flags, UE::MovieScene::ERunnerUpdateFlags::Flush))
|
|
{
|
|
bRequireFullFlush = true;
|
|
}
|
|
UpdateQueue.Add(FUpdateParamsAndContext{ FSimpleDelegate(), Context, { Instance, Flags } });
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::QueueUpdate(const FMovieSceneContext& Context, FInstanceHandle Instance, FSimpleDelegate&& OnFlushedDelegate, UE::MovieScene::ERunnerUpdateFlags Flags)
|
|
{
|
|
if (EnumHasAnyFlags(Flags, UE::MovieScene::ERunnerUpdateFlags::Flush))
|
|
{
|
|
bRequireFullFlush = true;
|
|
}
|
|
|
|
UpdateQueue.Add(FUpdateParamsAndContext{ OnFlushedDelegate, Context, { Instance, Flags } });
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::QueueFinalUpdate(FInstanceHandle InInstanceHandle)
|
|
{
|
|
return QueueFinalUpdateImpl(InInstanceHandle, FSimpleDelegate(), false);
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::QueueFinalUpdate(FInstanceHandle InInstanceHandle, FSimpleDelegate&& InOnLastFlushDelegate)
|
|
{
|
|
return QueueFinalUpdateImpl(InInstanceHandle, MoveTemp(InOnLastFlushDelegate), false);
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::QueueFinalUpdateAndDestroy(FInstanceHandle InInstanceHandle)
|
|
{
|
|
return QueueFinalUpdateImpl(InInstanceHandle, FSimpleDelegate(), true);
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::QueueFinalUpdateAndDestroy(FInstanceHandle InInstanceHandle, FSimpleDelegate&& InOnLastFlushDelegate)
|
|
{
|
|
return QueueFinalUpdateImpl(InInstanceHandle, MoveTemp(InOnLastFlushDelegate), true);
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::QueueFinalUpdateImpl(FInstanceHandle InInstanceHandle, FSimpleDelegate&& InOnLastFlushDelegate, bool bDestroyInstance)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
UMovieSceneEntitySystemLinker* Linker = GetLinker();
|
|
if (!Linker)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FInstanceRegistry* InstanceRegistry = GetInstanceRegistry();
|
|
if (!InstanceRegistry->IsHandleValid(InInstanceHandle))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(InInstanceHandle);
|
|
|
|
// If the following conditions are met, we can destroy this instance right away:
|
|
//
|
|
// 1. the instance has no entities left to unlink
|
|
// 2. we're not in the middle of an update loop
|
|
// 3. the instance has no current updates
|
|
//
|
|
const bool bCanFinishImmediately = Instance.CanFinishImmediately();
|
|
const ERunnerFlushState UnsafeDestroyMask = FlushState::Everything & ~(ERunnerFlushState::PostEvaluation | ERunnerFlushState::End);
|
|
const bool bSafeToDestroyNow = !EnumHasAnyFlags(FlushState, UnsafeDestroyMask);
|
|
if (bCanFinishImmediately && bSafeToDestroyNow && !HasQueuedUpdates(InInstanceHandle))
|
|
{
|
|
Instance.Finish();
|
|
Instance.PostEvaluation();
|
|
|
|
InOnLastFlushDelegate.ExecuteIfBound();
|
|
|
|
// PostEvaluation could have prompted a bunch of other logic that may have destroyed our instance handle so we have to check it for validity again
|
|
if (bDestroyInstance && InstanceRegistry->IsHandleValid(InInstanceHandle))
|
|
{
|
|
InstanceRegistry->DestroyInstance(InInstanceHandle);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// We queue up one last update that unlinks all of this instance's entities and lets systems tear down
|
|
// anything that has notable side-effects.
|
|
// It's possible that the instance already had an update request queued. We leave it in so it gets honored,
|
|
// and this second update request will be fulfilled in a second flush pass.
|
|
FUpdateParamsAndContext LastUpdate;
|
|
LastUpdate.Params.InstanceHandle = InInstanceHandle;
|
|
LastUpdate.Params.UpdateFlags = (bDestroyInstance ? (ERunnerUpdateFlags::Finish | ERunnerUpdateFlags::Destroy) : ERunnerUpdateFlags::Finish);
|
|
LastUpdate.OnFlushed = InOnLastFlushDelegate;
|
|
UpdateQueue.Add(LastUpdate);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::AbandonAndDestroyInstance(FInstanceHandle Instance)
|
|
{
|
|
if (QueueFinalUpdateAndDestroy(Instance))
|
|
{
|
|
Flush();
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::AbandonAndDestroyInstance(FInstanceHandle Instance, FSimpleDelegate&& InOnLastFlushDelegate)
|
|
{
|
|
if (QueueFinalUpdateAndDestroy(Instance, MoveTemp(InOnLastFlushDelegate)))
|
|
{
|
|
Flush();
|
|
}
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::StartEvaluation(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// We specifically only check whether the entity manager has changed since the last instantiation once
|
|
// to ensure that we are not vulnerable to infinite loops where components are added/removed in post-evaluation
|
|
const bool bStructureHadChanged = Linker->HasStructureChangedSinceLastRun();
|
|
|
|
if (!bStructureHadChanged && UpdateQueue.Num() == 0 && DissectedUpdates.Num() == 0)
|
|
{
|
|
// If nothing has changed, and we have no pending updates, we don't run any evaluation
|
|
FlushState = ERunnerFlushState::None;
|
|
return ERunnerFlushResult::Break;
|
|
}
|
|
|
|
if (!Linker->StartEvaluation())
|
|
{
|
|
return ERunnerFlushResult::Break;
|
|
}
|
|
|
|
EnterFlushState(ERunnerFlushState::Start);
|
|
|
|
// Our entity manager cannot be locked down for us to continue. Something must have left it locked if
|
|
// this check fails
|
|
FEntityManager& EntityManager = Linker->EntityManager;
|
|
check(!EntityManager.IsLockedDown());
|
|
|
|
EntityManager.SetDispatchThread(ENamedThreads::GameThread_Local);
|
|
EntityManager.SetGatherThread(ENamedThreads::GameThread_Local);
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::EndEvaluation(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
Linker->EndEvaluation();
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::FlushNext(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// The evaluation loop is set up as a crude state machine which allows us to break out of the
|
|
// loop and pick up where we left off next frame. Each bit within the FlushState bitmask defines
|
|
// a particular task that needs to execute. In this way we can loop the evaluation while there
|
|
// are updates pending by simply setting the necessary flags for each task. For convenience, the
|
|
// required tasks to perform another iteration are defined in ERunnerFlushState::LoopEval
|
|
|
|
// Guard the value of the CurrentPhase so it always returns back to ::None
|
|
TGuardValue<ESystemPhase> CurrentPhaseGuard(CurrentPhase, ESystemPhase::None);
|
|
TGuardValue<ERunnerFlushState> CurrentFlushStateGuard(CurrentFlushState, ERunnerFlushState::None);
|
|
|
|
// Step 1: Initialize all external flags and broadcast 'begin eval' events.
|
|
// NOTE: This is only ever called once, regardless of how many iterations we perform
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Start))
|
|
{
|
|
// EnterFlushState is called by StartEvaluation itself since it can fail
|
|
return StartEvaluation(Linker);
|
|
}
|
|
|
|
// Step 2: Execute any conditional recompiles for dirtied sequences.
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::ConditionalRecompile))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::ConditionalRecompile);
|
|
return GameThread_ConditionalRecompile(Linker);
|
|
}
|
|
|
|
// Step 3: Update sequence instances and import entities
|
|
// This is the entry-point for for each iteration
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Import))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::Import);
|
|
return GameThread_UpdateSequenceInstances(Linker);
|
|
}
|
|
|
|
// Step 4: Maybe re-update sequence instances if a recompile has ocurred after they were last updated, but before evaluation finished
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::ReimportAfterCompile))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::ReimportAfterCompile);
|
|
return GameThread_ReimportSequenceInstances(Linker);
|
|
}
|
|
|
|
// Step 5: Conditionally run the spawn phase of the system graph
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Spawn))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::Spawn);
|
|
return GameThread_SpawnPhase(Linker);
|
|
}
|
|
|
|
// Step 6: Conditionally run the instantiation phase of the system graph
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Instantiation))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::Instantiation);
|
|
return GameThread_InstantiationPhase(Linker);
|
|
}
|
|
|
|
// Step 7: Run the evaluation phase of the system graph
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Evaluation))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::Evaluation);
|
|
return GameThread_EvaluationPhase(Linker);
|
|
}
|
|
|
|
// Step 8: Run the finalization phase of the system graph, including legacy templates
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::Finalization))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::Finalization);
|
|
GameThread_EvaluationFinalizationPhase(Linker);
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
// Step 9: Trigger events
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::EventTriggers))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::EventTriggers);
|
|
GameThread_EventTriggerPhase(Linker);
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
// Step 10: Call PostEvaluation on all current sequence instances
|
|
//
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::PostEvaluation))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::PostEvaluation);
|
|
GameThread_PostEvaluationPhase(Linker);
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
// Step 11: Perform any clean up and broadcast 'end eval' events
|
|
// NOTE: Only ever called once regardless of how many iterations we perform
|
|
if (EnumHasAnyFlags(FlushState, ERunnerFlushState::End))
|
|
{
|
|
EnterFlushState(ERunnerFlushState::End);
|
|
EndEvaluation(Linker);
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
return ERunnerFlushResult::Break;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::Flush(double BudgetMs, UE::MovieScene::ERunnerFlushState TargetState)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// If we're not currently evaluating, start by flushing everything
|
|
if (FlushState == ERunnerFlushState::None)
|
|
{
|
|
FlushState = FlushState::Everything;
|
|
}
|
|
if (TargetState == ERunnerFlushState::None)
|
|
{
|
|
TargetState = FlushState::Everything;
|
|
}
|
|
|
|
FlushOutstanding(BudgetMs, TargetState);
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::FlushOutstanding(double BudgetMs, UE::MovieScene::ERunnerFlushState TargetState)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// Simple case is we have nothing outstanding, so just early return
|
|
if (FlushState == ERunnerFlushState::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If this runner is already being flushed, early return
|
|
if (CurrentFlushState != ERunnerFlushState::None)
|
|
{
|
|
UE_LOG(LogMovieSceneECS, Warning, TEXT("Cannot flush this runner while it is already being flushed outside of a re-entrancy window"));
|
|
return;
|
|
}
|
|
|
|
// If this runner's linker has been destroyed, early return
|
|
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
|
|
if (!Linker)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_RunnerFlush);
|
|
|
|
// We need to run the system from the game thread so we know we can fire events and callbacks from here.
|
|
check(IsInGameThread());
|
|
|
|
// Set the debug vizualizer's entity manager pointer, so all debugging happening here will show
|
|
// relevant information. We need to set it here instead of higher up because we could have, say,
|
|
// a blocking sequence triggering another blocking sequence via an event track. The nested call stack
|
|
// of the second sequence needs to show debug information relevant to its private linker, but when
|
|
// we return back up to the first sequence, which might still have another update round (such as
|
|
// the other side of the dissected update range around the event), we need to set the pointer back
|
|
// again.
|
|
TGuardValue<FEntityManager*> DebugVizGuard(GEntityManagerForDebuggingVisualizers, GetEntityManager());
|
|
|
|
// For the purposes of this function, None means that everything must be flushed
|
|
if (TargetState == ERunnerFlushState::None)
|
|
{
|
|
TargetState = FlushState::Everything;
|
|
}
|
|
|
|
const double BudgetSeconds = BudgetMs / 1000.f;
|
|
if (bRequireFullFlush)
|
|
{
|
|
while (EnumHasAnyFlags(FlushState, FlushState::Everything))
|
|
{
|
|
if (FlushNext(Linker) == ERunnerFlushResult::Break)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
bRequireFullFlush = false;
|
|
}
|
|
else if (BudgetSeconds > 0.0)
|
|
{
|
|
double StartTime = FPlatformTime::Seconds();
|
|
while (EnumHasAnyFlags(FlushState, TargetState))
|
|
{
|
|
ERunnerFlushResult Result = FlushNext(Linker);
|
|
if (Result == ERunnerFlushResult::Break)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (Result == ERunnerFlushResult::ContinueAllowBudget && FPlatformTime::Seconds() - StartTime >= BudgetSeconds)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (EnumHasAnyFlags(FlushState, TargetState))
|
|
{
|
|
if (FlushNext(Linker) == ERunnerFlushResult::Break)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are not right at the end of evaluation, we must check for compilation next time
|
|
if (FlushState != ERunnerFlushState::None && FlushState != ERunnerFlushState::End)
|
|
{
|
|
FlushState |= ERunnerFlushState::ConditionalRecompile;
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::ResetFlushState()
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
constexpr ERunnerFlushState StatesThatTriggerReset = ERunnerFlushState::Instantiation | ERunnerFlushState::Evaluation | ERunnerFlushState::Finalization | ERunnerFlushState::EventTriggers;
|
|
constexpr ERunnerFlushState StatesThatDontTriggerReset = ERunnerFlushState::Import | ERunnerFlushState::Spawn;
|
|
|
|
// We only need to reset the flush state if we are far enough through an evaluation
|
|
if (EnumHasAnyFlags(FlushState, StatesThatTriggerReset) && !EnumHasAnyFlags(FlushState, StatesThatDontTriggerReset))
|
|
{
|
|
// When resetting - we don't need to (or want to) re-import anything, we just want to re-run the current
|
|
// frame of updates
|
|
FlushState = UE::MovieScene::FlushState::LoopEval & ~UE::MovieScene::ERunnerFlushState::Import;
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::DiscardQueuedUpdates(FInstanceHandle Instance)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
for (int32 Index = UpdateQueue.Num()-1; Index >= 0; --Index)
|
|
{
|
|
if (UpdateQueue[Index].Params.InstanceHandle == Instance)
|
|
{
|
|
UpdateQueue.RemoveAt(Index, EAllowShrinking::No);
|
|
}
|
|
}
|
|
for (int32 Index = DissectedUpdates.Num()-1; Index >= 0; --Index)
|
|
{
|
|
if (DissectedUpdates[Index].InstanceHandle == Instance)
|
|
{
|
|
DissectedUpdates.RemoveAt(Index, EAllowShrinking::No);
|
|
}
|
|
}
|
|
|
|
for (int32 Index = CurrentInstances.Num()-1; Index >= 0; --Index)
|
|
{
|
|
if (CurrentInstances[Index].InstanceHandle == Instance)
|
|
{
|
|
CurrentInstances.RemoveAt(Index, EAllowShrinking::No);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::EnterFlushState(UE::MovieScene::ERunnerFlushState EnteredFlushState)
|
|
{
|
|
// Stop this state from being run again and allow the next one to run
|
|
FlushState &= ~EnteredFlushState;
|
|
// Mark this as the current state
|
|
CurrentFlushState = EnteredFlushState;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::SkipFlushState(UE::MovieScene::ERunnerFlushState FlushStateToSkip)
|
|
{
|
|
FlushState &= ~FlushStateToSkip;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_ConditionalRecompile(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMovieSceneEntitySystemRunner::GameThread_ConditionalRecompile);
|
|
|
|
FInstanceRegistry* InstanceRegistry = GetInstanceRegistry();
|
|
|
|
bool bAnyRecompile = false;
|
|
|
|
// If we have currently running instances, use those for the conditional compile
|
|
if (CurrentInstances.Num())
|
|
{
|
|
for (const FQueuedUpdateParams& UpdatedInstance : CurrentInstances)
|
|
{
|
|
if (InstanceRegistry->IsHandleValid(UpdatedInstance.InstanceHandle) && !EnumHasAnyFlags(UpdatedInstance.UpdateFlags, ERunnerUpdateFlags::Finish | ERunnerUpdateFlags::Destroy))
|
|
{
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(UpdatedInstance.InstanceHandle);
|
|
if (Instance.IsRootSequence() && Instance.ConditionalRecompile())
|
|
{
|
|
bAnyRecompile = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Otherwise process the update queue
|
|
else for (int32 UpdateIndex = 0; UpdateIndex < UpdateQueue.Num(); ++UpdateIndex)
|
|
{
|
|
const FUpdateParamsAndContext& Request = UpdateQueue[UpdateIndex];
|
|
if (InstanceRegistry->IsHandleValid(Request.Params.InstanceHandle) && !EnumHasAnyFlags(Request.Params.UpdateFlags, ERunnerUpdateFlags::Finish | ERunnerUpdateFlags::Destroy))
|
|
{
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(Request.Params.InstanceHandle);
|
|
if (Instance.IsRootSequence() && Instance.ConditionalRecompile())
|
|
{
|
|
bAnyRecompile = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAnyRecompile)
|
|
{
|
|
if (!EnumHasAnyFlags(FlushState, ERunnerFlushState::Import))
|
|
{
|
|
// If we are part-way through a phase that depends on NeedsLink/NeedsUnlink to match,
|
|
// we need to flush through those before we allow Reimport to run
|
|
if (Linker->EntityManager.ContainsComponent(FBuiltInComponentTypes::Get()->Tags.NeedsUnlink) || Linker->EntityManager.ContainsComponent(FBuiltInComponentTypes::Get()->Tags.NeedsLink))
|
|
{
|
|
TGuardValue<ERunnerFlushState> FlushStateGuard(CurrentFlushState, ERunnerFlushState::None);
|
|
FlushOutstanding(0.0, ERunnerFlushState::Spawn | ERunnerFlushState::Instantiation);
|
|
}
|
|
|
|
// If we have already imported everything, we have to run a reimport to make sure everything is imported correctly
|
|
FlushState |= ERunnerFlushState::ReimportAfterCompile;
|
|
}
|
|
|
|
// If a recompile has occurred, we need to make sure we run everything again (except for checking for another recompile!)
|
|
FlushState |= (FlushState::LoopEval & ~(ERunnerFlushState::ConditionalRecompile | ERunnerFlushState::Import));
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
// Do not allow us to get stuck in an infinite loop checking for compilation
|
|
return ERunnerFlushResult::ContinueNoBudgeting;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_UpdateSequenceInstances(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMovieSceneEntitySystemRunner::GameThread_UpdateSequenceInstances);
|
|
|
|
// Also reset the capture source scope so that each group of sequences tied to a given linker starts
|
|
// with a clean slate.
|
|
TGuardValue<FScopedPreAnimatedCaptureSource*> CaptureSourceGuard(FScopedPreAnimatedCaptureSource::GetCaptureSourcePtr(), nullptr);
|
|
|
|
FInstanceRegistry* InstanceRegistry = GetInstanceRegistry();
|
|
|
|
if (DissectedUpdates.Num() == 0)
|
|
{
|
|
TArray<TRange<FFrameTime>> Dissections;
|
|
TBitArray<> UpdatedSequenceInstances;
|
|
|
|
TArray<FUpdateParamsAndContext> TempUpdateQueue;
|
|
Swap(TempUpdateQueue, UpdateQueue);
|
|
|
|
DissectedUpdates.Reserve(TempUpdateQueue.Num());
|
|
|
|
for (int32 UpdateIndex = 0; UpdateIndex < TempUpdateQueue.Num(); ++UpdateIndex)
|
|
{
|
|
FUpdateParamsAndContext& Request = TempUpdateQueue[UpdateIndex];
|
|
const int32 InstanceID = Request.Params.InstanceHandle.InstanceID;
|
|
|
|
if (!InstanceRegistry->IsHandleValid(Request.Params.InstanceHandle))
|
|
{
|
|
continue;
|
|
}
|
|
else if (UpdatedSequenceInstances.IsValidIndex(InstanceID) && UpdatedSequenceInstances[InstanceID] == true)
|
|
{
|
|
// We already have an update for this instance, so we queue it up again for the next flush
|
|
UpdateQueue.Add(Request);
|
|
continue;
|
|
}
|
|
|
|
// Set the bit to indicate this sequence is being updated
|
|
UpdatedSequenceInstances.PadToNum(InstanceID + 1, false);
|
|
UpdatedSequenceInstances[InstanceID] = true;
|
|
|
|
// If the request is to destroy the instance, we must assume it is no longer valid and should
|
|
// not be updated
|
|
if (EnumHasAnyFlags(Request.Params.UpdateFlags, ERunnerUpdateFlags::Finish | ERunnerUpdateFlags::Destroy))
|
|
{
|
|
DissectedUpdates.Add(FDissectedUpdate{ MoveTemp(Request.OnFlushed), Request.Context, Request.Params.InstanceHandle, MAX_int32, Request.Params.UpdateFlags });
|
|
MarkForUpdate(Request.Params.InstanceHandle, Request.Params.UpdateFlags);
|
|
continue;
|
|
}
|
|
|
|
// Give the instance an opportunity to dissect the range into distinct evaluations
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(Request.Params.InstanceHandle);
|
|
if (!Instance.IsRootSequence())
|
|
{
|
|
TSharedRef<const FSharedPlaybackState> SharedPlaybackState = Instance.GetSharedPlaybackState();
|
|
UMovieSceneSequence* RootSequence = SharedPlaybackState->GetRootSequence();
|
|
UMovieSceneSequence* SubSequence = SharedPlaybackState->GetSequence(Instance.GetSequenceID());
|
|
|
|
ensureMsgf(Instance.IsRootSequence(), TEXT("Update request received for a non-root sequence ID 0x%08X (%s) in root-sequence %s. This is not supported."),
|
|
Instance.GetSequenceID().GetInternalValue(),
|
|
SubSequence ? *SubSequence->GetName() : TEXT("<nullptr>"),
|
|
RootSequence ? *RootSequence->GetName() : TEXT("<nullptr>")
|
|
);
|
|
continue;
|
|
}
|
|
|
|
Instance.DissectContext(Request.Context, Dissections);
|
|
|
|
if (Dissections.Num() != 0)
|
|
{
|
|
if (Request.Context.GetDirection() == EPlayDirection::Backwards)
|
|
{
|
|
Algo::Reverse(Dissections);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < Dissections.Num() - 1; ++Index)
|
|
{
|
|
// Never finish or destroy sequence instances until the _last_ dissected update
|
|
ERunnerUpdateFlags Flags = Request.Params.UpdateFlags & ~ERunnerUpdateFlags::FinalDissectionMask;
|
|
|
|
FDissectedUpdate Dissection{
|
|
FSimpleDelegate(), // Never trigger the OnFlushed delegate until the final dissection
|
|
FMovieSceneContext(FMovieSceneEvaluationRange(Dissections[Index], Request.Context.GetFrameRate(), Request.Context.GetDirection()), Request.Context.GetStatus()),
|
|
Request.Params.InstanceHandle,
|
|
Index,
|
|
Flags
|
|
};
|
|
DissectedUpdates.Add(Dissection);
|
|
|
|
// Only mark the first dissection for update this round
|
|
if (Index == 0)
|
|
{
|
|
MarkForUpdate(Request.Params.InstanceHandle, Flags);
|
|
}
|
|
}
|
|
|
|
// Add the last one with MAX_int32 so it gets evaluated with all the others in this flush
|
|
FDissectedUpdate Dissection{
|
|
MoveTemp(Request.OnFlushed),
|
|
FMovieSceneContext(FMovieSceneEvaluationRange(Dissections.Last(), Request.Context.GetFrameRate(), Request.Context.GetDirection()), Request.Context.GetStatus()),
|
|
Request.Params.InstanceHandle,
|
|
MAX_int32,
|
|
Request.Params.UpdateFlags
|
|
};
|
|
DissectedUpdates.Add(Dissection);
|
|
|
|
Dissections.Reset();
|
|
}
|
|
else
|
|
{
|
|
DissectedUpdates.Add(FDissectedUpdate{ MoveTemp(Request.OnFlushed), Request.Context, Request.Params.InstanceHandle, MAX_int32, Request.Params.UpdateFlags });
|
|
MarkForUpdate(Request.Params.InstanceHandle, Request.Params.UpdateFlags);
|
|
}
|
|
}
|
|
|
|
Algo::SortBy(DissectedUpdates, &FDissectedUpdate::Order);
|
|
}
|
|
else
|
|
{
|
|
// Look for the next batch of updates, and mark the respective sequence instances as currently updating.
|
|
const int32 PredicateOrder = DissectedUpdates[0].Order;
|
|
for (int32 Index = 0; Index < DissectedUpdates.Num() && DissectedUpdates[Index].Order == PredicateOrder; ++Index)
|
|
{
|
|
FDissectedUpdate& Update = DissectedUpdates[Index];
|
|
MarkForUpdate(Update.InstanceHandle, Update.UpdateFlags);
|
|
}
|
|
}
|
|
|
|
// If we have no instances marked for update, we are running an evaluation probably because some
|
|
// structural changes have occurred in the entity manager (out of date instantiation serial number
|
|
// in the linker). So we mark everything for update, so that PreEvaluation/PostEvaluation callbacks
|
|
// and legacy templates are correctly executed.
|
|
if (CurrentInstances.Num() == 0)
|
|
{
|
|
for (const FSequenceInstance& Instance : InstanceRegistry->GetSparseInstances())
|
|
{
|
|
MarkForUpdate(Instance.GetInstanceHandle(), ERunnerUpdateFlags::None);
|
|
}
|
|
}
|
|
|
|
AccumulatedUpdateFlags = ESequenceInstanceUpdateFlags::None;
|
|
|
|
// Let sequence instances do any pre-evaluation work.
|
|
for (const FQueuedUpdateParams& UpdatedInstance : CurrentInstances)
|
|
{
|
|
if (!EnumHasAnyFlags(UpdatedInstance.UpdateFlags, ERunnerUpdateFlags::Destroy))
|
|
{
|
|
FSequenceInstance& SequenceInstance = InstanceRegistry->MutateInstance(UpdatedInstance.InstanceHandle);
|
|
|
|
AccumulatedUpdateFlags |= SequenceInstance.GetUpdateFlags();
|
|
uint16 PlayerIndex = FPlayerIndexPlaybackCapability::GetPlayerIndex(SequenceInstance.GetSharedPlaybackState());
|
|
if (PlayerIndex != (uint16)-1)
|
|
{
|
|
IMovieScenePlayer::SetIsEvaluatingFlag(PlayerIndex, true);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(SequenceInstance.GetUpdateFlags(), ESequenceInstanceUpdateFlags::NeedsPreEvaluation))
|
|
{
|
|
SequenceInstance.PreEvaluation();
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: IncrementSystemSerial must be called before any instance updates are made
|
|
// to ensure that up-to-date versions are used inside FEntityManager::OnStructureChanged
|
|
Linker->EntityManager.IncrementSystemSerial();
|
|
|
|
// Update all systems
|
|
if (DissectedUpdates.Num() != 0)
|
|
{
|
|
TGuardValue<bool> IsUpdatingSequenceGuard(bIsUpdatingSequence, true);
|
|
|
|
const int32 PredicateOrder = DissectedUpdates[0].Order;
|
|
int32 Index = 0;
|
|
for (; Index < DissectedUpdates.Num() && DissectedUpdates[Index].Order == PredicateOrder; ++Index)
|
|
{
|
|
FDissectedUpdate& Update = DissectedUpdates[Index];
|
|
|
|
// Always forward the OnFlushed delegate to be called at the end of the frame, even if the instance is no longer valid
|
|
if (Update.OnFlushed.IsBound())
|
|
{
|
|
OnFlushedDelegates.Add(MoveTemp(Update.OnFlushed));
|
|
|
|
// If we have any on-flushed delegates then we have to do a full Post-Eval phase
|
|
AccumulatedUpdateFlags |= ESequenceInstanceUpdateFlags::NeedsPostEvaluation;
|
|
}
|
|
|
|
if (ensure(InstanceRegistry->IsHandleValid(Update.InstanceHandle)))
|
|
{
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(Update.InstanceHandle);
|
|
|
|
if (EnumHasAnyFlags(Update.UpdateFlags, ERunnerUpdateFlags::Finish))
|
|
{
|
|
// Context is irrelevant for Finishing sequences
|
|
Instance.Finish();
|
|
}
|
|
else if (EnumHasAnyFlags(Update.UpdateFlags, ERunnerUpdateFlags::Destroy))
|
|
{
|
|
ensure(Instance.HasFinished());
|
|
}
|
|
else
|
|
{
|
|
Instance.Update(Update.Context);
|
|
}
|
|
}
|
|
}
|
|
DissectedUpdates.RemoveAt(0, Index);
|
|
}
|
|
|
|
#if DO_GUARD_SLOW
|
|
// Check that there are no duplicates in the CurrentInstances array
|
|
{
|
|
TBitArray<> VisitedBits;
|
|
VisitedBits.Reserve(InstanceRegistry->GetSparseInstances().GetMaxIndex());
|
|
for (const FQueuedUpdateParams& Update : CurrentInstances)
|
|
{
|
|
const int32 Index = Update.InstanceHandle.InstanceID;
|
|
|
|
checkf(!VisitedBits.IsValidIndex(Index) || VisitedBits[Index] == false, TEXT("Multiple updates exist for the same sequence instance. This indicates a bookkeeping error with the CurrentInstances array."));
|
|
|
|
VisitedBits.PadToNum(Index + 1, false);
|
|
VisitedBits[Index] = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_ReimportSequenceInstances(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// Only called after a sequence has been recompiled after we have already updated the current instances
|
|
// This allows us to re-update all the sequence instances in case anything has changed
|
|
|
|
TGuardValue<bool> IsUpdatingSequenceGuard(bIsUpdatingSequence, true);
|
|
|
|
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
|
|
|
|
// NOTE: IncrementSystemSerial must be called before any instance updates are made
|
|
// to ensure that up-to-date versions are used inside FEntityManager::OnStructureChanged
|
|
Linker->EntityManager.IncrementSystemSerial();
|
|
|
|
// Operate on a copy of the update params since it is possible CurrentInstances can change if sequence instances are destroyed
|
|
TArray<FQueuedUpdateParams> CurrentInstancesCopy(MoveTemp(CurrentInstances));
|
|
CurrentInstances.Reset(CurrentInstancesCopy.Num());
|
|
|
|
for (const FQueuedUpdateParams& UpdatedInstance : CurrentInstancesCopy)
|
|
{
|
|
if (!InstanceRegistry->IsHandleValid(UpdatedInstance.InstanceHandle))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(UpdatedInstance.UpdateFlags, ERunnerUpdateFlags::Destroy | ERunnerUpdateFlags::Finish))
|
|
{
|
|
FSequenceInstance& SequenceInstance = InstanceRegistry->MutateInstance(UpdatedInstance.InstanceHandle);
|
|
// Need to check whether this has actually finished yet or not
|
|
if (!SequenceInstance.HasFinished())
|
|
{
|
|
SequenceInstance.Finish();
|
|
}
|
|
|
|
// If it is being finished or destroyed, just re-add this update back.
|
|
CurrentInstances.Add(UpdatedInstance);
|
|
}
|
|
else
|
|
{
|
|
FSequenceInstance& SequenceInstance = InstanceRegistry->MutateInstance(UpdatedInstance.InstanceHandle);
|
|
// Only update root instances
|
|
if (SequenceInstance.IsRootSequence())
|
|
{
|
|
// Add root sequences back to the CurrentInstances array. When we call Update any active sub-sequences will be re-added via MarkForUpdate
|
|
CurrentInstances.Add(UpdatedInstance);
|
|
SequenceInstance.Update(SequenceInstance.GetContext());
|
|
}
|
|
}
|
|
}
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_SpawnPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
check(GameThread == ENamedThreads::GameThread || GameThread == ENamedThreads::GameThread_Local);
|
|
|
|
|
|
CurrentPhase = ESystemPhase::Spawn;
|
|
|
|
FInstanceRegistry* InstanceRegistry = GetInstanceRegistry();
|
|
|
|
const bool bInstantiationDirty = Linker->HasStructureChangedSinceLastRun() || InstanceRegistry->HasInvalidatedBindings();
|
|
|
|
FGraphEventArray AllTasks;
|
|
|
|
Linker->AutoLinkRelevantSystems();
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Run the spawn phase if there were any changes to the current entity instantiations, and wait on the result
|
|
{
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_SpawnPhase);
|
|
if (bInstantiationDirty)
|
|
{
|
|
// The spawn phase can queue events to trigger from the event tracks.
|
|
bCanQueueEventTriggers = true;
|
|
{
|
|
Linker->SystemGraph.ExecutePhase(ESystemPhase::Spawn, Linker, AllTasks);
|
|
}
|
|
bCanQueueEventTriggers = false;
|
|
|
|
// We don't open a re-entrancy window, however, because there's no way we can recursively evaluate things at this point... too many
|
|
// things are in an intermediate state. So events triggered as PreSpawn/PostSpawn can't be wired to something that starts a sequence.
|
|
if (EventTriggers.IsBound())
|
|
{
|
|
EventTriggers.Broadcast();
|
|
EventTriggers.Clear();
|
|
}
|
|
}
|
|
|
|
// If there were any tasks created, we need to wait on them before proceeding. This is rare for the spawn phase.
|
|
if (AllTasks.Num() != 0)
|
|
{
|
|
FGraphEventRef SpawnEvent = TGraphTask<FNullGraphTask>::CreateTask(&AllTasks, ENamedThreads::GameThread)
|
|
.ConstructAndDispatchWhenReady(TStatId(), GameThread);
|
|
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(SpawnEvent, ENamedThreads::GameThread_Local);
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_PostSpawnEvent);
|
|
Linker->Events.PostSpawnEvent.Broadcast(Linker);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Only run the instantiation phase if there is anything to instantiate. This must come after the spawn phase because new instantiations may
|
|
// be created during the spawn phase
|
|
const bool bAnyPending = Linker->EntityManager.ContainsAnyComponent(FBuiltInComponentTypes::Get()->RequiresInstantiationMask) || InstanceRegistry->HasInvalidatedBindings();
|
|
if (bInstantiationDirty == false && bAnyPending == false)
|
|
{
|
|
SkipFlushState(ERunnerFlushState::Instantiation);
|
|
}
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_InstantiationPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_InstantiationPhase);
|
|
|
|
check(GameThread == ENamedThreads::GameThread || GameThread == ENamedThreads::GameThread_Local);
|
|
|
|
CurrentPhase = ESystemPhase::Instantiation;
|
|
|
|
FGraphEventArray AllTasks;
|
|
Linker->SystemGraph.ExecutePhase(ESystemPhase::Instantiation, Linker, AllTasks);
|
|
|
|
// If there were any tasks created, we need to wait on them before proceeding. This is rare for the instantiation phase.
|
|
if (AllTasks.Num() != 0)
|
|
{
|
|
FGraphEventRef InstantiationEvent = TGraphTask<FNullGraphTask>::CreateTask(&AllTasks, ENamedThreads::GameThread)
|
|
.ConstructAndDispatchWhenReady(TStatId(), GameThread);
|
|
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(InstantiationEvent, ENamedThreads::GameThread_Local);
|
|
}
|
|
|
|
return GameThread_PostInstantiation(Linker);
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_PostInstantiation(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_PostInstantiation);
|
|
|
|
check(GameThread == ENamedThreads::GameThread || GameThread == ENamedThreads::GameThread_Local);
|
|
|
|
Linker->PostInstantation();
|
|
|
|
FEntityManager& EntityManager = Linker->EntityManager;
|
|
FBuiltInComponentTypes* BuiltInComponentTypes = FBuiltInComponentTypes::Get();
|
|
|
|
// Nothing needs linking, caching or restoring any more
|
|
FRemoveMultipleMutation Mutation;
|
|
Mutation.MaskToRemove = BuiltInComponentTypes->RequiresInstantiationMask;
|
|
// Inverse the filter because MaskToRemove is applied as a binary AND against all matching allocations.
|
|
// Therefore, any set bits in RequiresInstantiationMask will be removed from components
|
|
Mutation.MaskToRemove.BitwiseNOT();
|
|
|
|
FEntityComponentFilter Filter = FEntityComponentFilter().Any(BuiltInComponentTypes->RequiresInstantiationMask);
|
|
EntityManager.MutateAll(Filter, Mutation);
|
|
|
|
// Free anything that has been unlinked
|
|
EntityManager.FreeEntities(FEntityComponentFilter().All({ BuiltInComponentTypes->Tags.NeedsUnlink }));
|
|
|
|
Linker->AutoUnlinkIrrelevantSystems();
|
|
|
|
EntityManager.Compact();
|
|
|
|
if (FEntitySystemScheduler::IsCustomSchedulingEnabled())
|
|
{
|
|
Linker->SystemGraph.ReconstructTaskSchedule(&Linker->EntityManager);
|
|
}
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
UE::MovieScene::ERunnerFlushResult FMovieSceneEntitySystemRunner::GameThread_EvaluationPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_EvaluationPhase);
|
|
|
|
CurrentPhase = ESystemPhase::Evaluation;
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------------------------------
|
|
// Step 2: Run the evaluation phase. The entity manager is locked down for this phase, meaning no changes to entity-component structure is allowed
|
|
// This vastly simplifies the concurrent handling of entity component allocations
|
|
Linker->EntityManager.LockDown();
|
|
|
|
checkf(!Linker->EntityManager.ContainsComponent(FBuiltInComponentTypes::Get()->Tags.NeedsUnlink), TEXT("Stale entities remain in the entity manager during evaluation - these should have been destroyed during the instantiation phase. Did it run?"));
|
|
|
|
if (FEntitySystemScheduler::IsCustomSchedulingEnabled())
|
|
{
|
|
Linker->SystemGraph.ScheduleTasks(&Linker->EntityManager);
|
|
}
|
|
|
|
FGraphEventArray AllTasks;
|
|
Linker->SystemGraph.ExecutePhase(ESystemPhase::Evaluation, Linker, AllTasks);
|
|
|
|
if (AllTasks.Num() != 0)
|
|
{
|
|
FGraphEventRef EvaluationEvent = TGraphTask<FNullGraphTask>::CreateTask(&AllTasks, ENamedThreads::GameThread)
|
|
.ConstructAndDispatchWhenReady(TStatId(), GameThread);
|
|
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(EvaluationEvent, ENamedThreads::GameThread_Local);
|
|
}
|
|
|
|
Linker->EntityManager.ReleaseLockDown();
|
|
|
|
if ( !EnumHasAnyFlags(AccumulatedUpdateFlags, ESequenceInstanceUpdateFlags::HasLegacyTemplates)
|
|
&& Linker->SystemGraph.NumInPhase(ESystemPhase::Finalization) == 0
|
|
&& !EventTriggers.IsBound())
|
|
{
|
|
// Skip Finalization if there's no need for it
|
|
SkipFlushState(ERunnerFlushState::Finalization | ERunnerFlushState::EventTriggers);
|
|
}
|
|
|
|
return ERunnerFlushResult::ContinueAllowBudget;
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::GameThread_EvaluationFinalizationPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
check(GameThread == ENamedThreads::GameThread || GameThread == ENamedThreads::GameThread_Local);
|
|
|
|
CurrentPhase = ESystemPhase::Finalization;
|
|
|
|
// Post-eval events can be queued during the finalization phase so let's open that up.
|
|
// The events are actually executed a bit later, in GameThread_EventTriggerPhase.
|
|
bCanQueueEventTriggers = true;
|
|
{
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_FinalizationPhase);
|
|
|
|
FInstanceRegistry* InstanceRegistry = GetInstanceRegistry();
|
|
|
|
// Iterate on a copy of our current instances, since LegacyEvaluator->Evaluate() could change the instance handle, which would affect PostEvaluationPhase
|
|
TArray<FQueuedUpdateParams> CurrentInstancesCopy(CurrentInstances);
|
|
for (const FQueuedUpdateParams& UpdateParams : CurrentInstancesCopy)
|
|
{
|
|
if (InstanceRegistry->IsHandleValid(UpdateParams.InstanceHandle))
|
|
{
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(UpdateParams.InstanceHandle);
|
|
if (Instance.IsRootSequence())
|
|
{
|
|
Instance.RunLegacyTrackTemplates();
|
|
}
|
|
}
|
|
}
|
|
|
|
FGraphEventArray Tasks;
|
|
Linker->SystemGraph.ExecutePhase(ESystemPhase::Finalization, Linker, Tasks);
|
|
checkf(Tasks.Num() == 0, TEXT("Cannot dispatch new tasks during finalization"));
|
|
}
|
|
bCanQueueEventTriggers = false;
|
|
|
|
if (!EventTriggers.IsBound())
|
|
{
|
|
// Skip event triggers if there are none
|
|
SkipFlushState(ERunnerFlushState::EventTriggers);
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::GameThread_EventTriggerPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// Execute any queued events from the evaluation finalization phase.
|
|
if (EventTriggers.IsBound())
|
|
{
|
|
// Event triggers are allowed to be re-entrant
|
|
FMovieSceneEntitySystemEvaluationReentrancyWindow Window(this, Linker);
|
|
|
|
// Trigger from a temporary delegate to ensure that re-entrant evaluations do not re-trigger events
|
|
FMovieSceneEntitySystemEventTriggers TmpEventTriggers;
|
|
|
|
Swap(TmpEventTriggers, EventTriggers);
|
|
EventTriggers.Clear();
|
|
|
|
TmpEventTriggers.Broadcast();
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::GameThread_PostEvaluationPhase(UMovieSceneEntitySystemLinker* Linker)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
SCOPE_CYCLE_COUNTER(MovieSceneEval_PostEvaluationPhase);
|
|
|
|
// Now run the post-evaluation logic so that we can safely handle broadcast events (like OnFinished)
|
|
// that trigger some new evaluations (such as connecting it to another sequence's Play in Blueprint).
|
|
//
|
|
// If we are the global linker (and not a "private" linker, as is the case with "blocking" sequences),
|
|
// we may find ourselves in a re-entrant call, which means we need to save our state here and restore
|
|
// it afterwards. We also iterate on a copy of our current instances, since a re-entrant call would
|
|
// modify that array.
|
|
|
|
// Temporarily cache the pending updates and dissected updates so we can preserve re-entrant ordering
|
|
// by appending the arrays afterwards. This ensures that any updates queued during a re-entrant call
|
|
// get evaluated _before_ the updates that are specified in the outer scope, even if the evaluation was
|
|
// budgeted and didn't fully flush, whilst simultaneously correctly only flushing inner updates if a
|
|
// full flush is performed.
|
|
TArray<FQueuedUpdateParams> TmpCurrentInstances;
|
|
TArray<FUpdateParamsAndContext> TmpUpdateQueue;
|
|
TArray<FDissectedUpdate> TmpDissectedUpdates;
|
|
TArray<FInstanceHandle> TmpDestroyInstances;
|
|
TArray<FSimpleDelegate> TmpOnFlushedDelegates;
|
|
|
|
Swap(UpdateQueue, TmpUpdateQueue);
|
|
Swap(DissectedUpdates, TmpDissectedUpdates);
|
|
Swap(CurrentInstances, TmpCurrentInstances);
|
|
Swap(OnFlushedDelegates, TmpOnFlushedDelegates);
|
|
|
|
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
|
|
|
|
{
|
|
FMovieSceneEntitySystemEvaluationReentrancyWindow Window(this, Linker);
|
|
|
|
for (const FQueuedUpdateParams& UpdateParams : TmpCurrentInstances)
|
|
{
|
|
// We must check for validity here because the cache handles may have become invalid
|
|
// during this iteration (since there is a re-entrancy window open)
|
|
if (InstanceRegistry->IsHandleValid(UpdateParams.InstanceHandle))
|
|
{
|
|
FSequenceInstance& Instance = InstanceRegistry->MutateInstance(UpdateParams.InstanceHandle);
|
|
|
|
Instance.Ledger.UnlinkOneShots(Linker);
|
|
uint16 PlayerIndex = FPlayerIndexPlaybackCapability::GetPlayerIndex(Instance.GetSharedPlaybackState());
|
|
if (PlayerIndex != (uint16)-1)
|
|
{
|
|
IMovieScenePlayer::SetIsEvaluatingFlag(PlayerIndex, false);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(Instance.GetUpdateFlags(), ESequenceInstanceUpdateFlags::NeedsPostEvaluation))
|
|
{
|
|
Instance.PostEvaluation();
|
|
}
|
|
|
|
if (EnumHasAnyFlags(UpdateParams.UpdateFlags, ERunnerUpdateFlags::Destroy))
|
|
{
|
|
TmpDestroyInstances.Add(UpdateParams.InstanceHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FSimpleDelegate& OnFlushed : TmpOnFlushedDelegates)
|
|
{
|
|
OnFlushed.Execute();
|
|
}
|
|
}
|
|
|
|
// Destroy any instances that need destroying
|
|
for (FInstanceHandle Handle : TmpDestroyInstances)
|
|
{
|
|
if (InstanceRegistry->IsHandleValid(Handle))
|
|
{
|
|
InstanceRegistry->DestroyInstance(Handle);
|
|
}
|
|
}
|
|
|
|
// If we had any updates _before_ we called the external callbacks (which are stored in TmpUpdateQueue), make sure those are maintained first
|
|
Swap(TmpUpdateQueue, UpdateQueue);
|
|
|
|
// TmpUpdateQueue now contains any updates that were queued _during_ the external callbacks, so we append those to our old queue (which is now stored back in UpdateQueue)
|
|
if (TmpUpdateQueue.Num() > 0)
|
|
{
|
|
UpdateQueue.Append(TmpUpdateQueue);
|
|
}
|
|
|
|
// Do the same with the dissected updates
|
|
Swap(TmpDissectedUpdates, DissectedUpdates);
|
|
if (TmpDissectedUpdates.Num() > 0)
|
|
{
|
|
DissectedUpdates.Append(TmpDissectedUpdates);
|
|
// Important: we do not sort the Dissected updates here to ensure that the previously populated entries are evaluated first
|
|
}
|
|
|
|
// If we have any pending updates, we need to run another evaluation.
|
|
// This will cause us to effectively loop back to ERunnerFlushState::Import
|
|
// which will pick up the next updates
|
|
if (UpdateQueue.Num() > 0 || DissectedUpdates.Num() > 0)
|
|
{
|
|
FlushState = FlushState::LoopEval;
|
|
}
|
|
}
|
|
|
|
void FMovieSceneEntitySystemRunner::MarkForUpdate(FInstanceHandle InInstanceHandle, UE::MovieScene::ERunnerUpdateFlags UpdateFlags)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// If we are reimporting after a compile, we might be finishing/destroying a sequence for the first time and so the call to
|
|
// Finish from GameThread_UpdateSequenceInstances may have been missed. In this case we have to check whether the sequence
|
|
// needs finishing so we don't have entities left in the ledger when it comes to being destroyed.
|
|
if (bIsUpdatingSequence && EnumHasAnyFlags(UpdateFlags, ERunnerUpdateFlags::Finish | ERunnerUpdateFlags::Destroy))
|
|
{
|
|
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
|
|
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
|
|
FSequenceInstance& SequenceInstance = InstanceRegistry->MutateInstance(InInstanceHandle);
|
|
|
|
// Need to check whether this has actually finished yet or not
|
|
if (!SequenceInstance.HasFinished())
|
|
{
|
|
SequenceInstance.Finish();
|
|
}
|
|
}
|
|
|
|
CurrentInstances.Add(FQueuedUpdateParams{ InInstanceHandle, UpdateFlags });
|
|
}
|
|
|
|
FMovieSceneEntitySystemEventTriggers& FMovieSceneEntitySystemRunner::GetQueuedEventTriggers()
|
|
{
|
|
checkf(bCanQueueEventTriggers, TEXT("Can't queue event triggers at this point in the update loop."));
|
|
return EventTriggers;
|
|
}
|
|
|
|
bool FMovieSceneEntitySystemRunner::FlushSingleEvaluationPhase()
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
if (!ensureMsgf(
|
|
!IsCurrentlyEvaluating() && CurrentPhase == ESystemPhase::None,
|
|
TEXT("Can't run nested flush phase while the runner is evaluating and no re-entrancy window is open")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
|
|
if (!ensureMsgf(Linker, TEXT("Runner doesn't have a valid linker")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TGuardValue<ESystemPhase> PhaseGuard(CurrentPhase, ESystemPhase::Evaluation);
|
|
|
|
Linker->EntityManager.LockDown();
|
|
|
|
if (FEntitySystemScheduler::IsCustomSchedulingEnabled())
|
|
{
|
|
Linker->SystemGraph.ScheduleTasks(&Linker->EntityManager);
|
|
}
|
|
|
|
FGraphEventArray AllTasks;
|
|
Linker->SystemGraph.ExecutePhase(ESystemPhase::Evaluation, Linker, AllTasks);
|
|
|
|
if (AllTasks.Num() != 0)
|
|
{
|
|
FTaskGraphInterface::Get().WaitUntilTasksComplete(AllTasks, ENamedThreads::GameThread_Local);
|
|
}
|
|
|
|
Linker->EntityManager.ReleaseLockDown();
|
|
|
|
return true;
|
|
}
|
|
|