Files
UnrealEngine/Engine/Source/Runtime/Experimental/Animation/Constraints/Private/ConstraintSubsystem.cpp
2025-05-18 13:04:45 +08:00

370 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ConstraintSubsystem.h"
#include "Containers/Ticker.h"
#include "Engine/Engine.h"
#include "ConstraintsManager.h"
#include "Algo/ForEach.h"
#include "Misc/CoreDelegates.h"
//needs to be static to avoid system getting deleted with dangling handles.
FDelegateHandle UConstraintSubsystem::OnWorldInitHandle;
FDelegateHandle UConstraintSubsystem::OnWorldCleanupHandle;
FDelegateHandle UConstraintSubsystem::OnPostGarbageCollectHandle;
UConstraintSubsystem::UConstraintSubsystem()
{
}
void UConstraintSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (GEngine && GEngine->IsInitialized())
{
RegisterWorldDelegates();
}
else
{
FCoreDelegates::OnPostEngineInit.AddUObject(this, &UConstraintSubsystem::RegisterWorldDelegates);
}
SetFlags(RF_Transactional);
}
void UConstraintSubsystem::RegisterWorldDelegates()
{
OnWorldInitHandle = FWorldDelegates::OnPreWorldInitialization.AddStatic(&UConstraintSubsystem::OnWorldInit);
OnWorldCleanupHandle = FWorldDelegates::OnWorldCleanup.AddStatic(&UConstraintSubsystem::OnWorldCleanup);
OnPostGarbageCollectHandle = FCoreUObjectDelegates::GetPostGarbageCollect().AddStatic(&UConstraintSubsystem::OnPostGarbageCollect);
FCoreDelegates::OnPostEngineInit.RemoveAll(this);
}
void UConstraintSubsystem::Deinitialize()
{
for (int32 Index = ConstraintsInWorld.Num() - 1; Index >= 0; --Index)
{
ConstraintsInWorld[Index].RemoveConstraints(ConstraintsInWorld[Index].World.Get());
}
ConstraintsInWorld.Reset();
FWorldDelegates::OnPreWorldInitialization.Remove(OnWorldInitHandle);
FWorldDelegates::OnWorldCleanup.Remove(OnWorldCleanupHandle);
FCoreUObjectDelegates::GetPostGarbageCollect().Remove(OnPostGarbageCollectHandle);
Super::Deinitialize();
}
UConstraintSubsystem* UConstraintSubsystem::Get()
{
if (GEngine && GEngine->IsInitialized())
{
return GEngine->GetEngineSubsystem<UConstraintSubsystem>();
}
return nullptr;
}
int32 UConstraintSubsystem::GetConstraintsInWorldIndex(const UWorld* InWorld) const
{
return ConstraintsInWorld.IndexOfByPredicate([InWorld](const FConstraintsInWorld& ConstInWorld)
{
return ConstInWorld.World.Get() == InWorld;
});
}
const FConstraintsInWorld* UConstraintSubsystem::ConstraintsInWorldFind(const UWorld* InWorld) const
{
if (bNeedsCleanup)
{
CleanupInvalidConstraints();
}
const int32 Index = GetConstraintsInWorldIndex(InWorld);
return Index != INDEX_NONE ? &ConstraintsInWorld[Index] : nullptr;
}
FConstraintsInWorld* UConstraintSubsystem::ConstraintsInWorldFind(const UWorld* InWorld)
{
if (bNeedsCleanup)
{
CleanupInvalidConstraints();
}
const int32 Index = GetConstraintsInWorldIndex(InWorld);
return Index != INDEX_NONE ? &ConstraintsInWorld[Index] : nullptr;
}
FConstraintsInWorld& UConstraintSubsystem::ConstraintsInWorldFindOrAdd(UWorld* InWorld)
{
if (bNeedsCleanup)
{
CleanupInvalidConstraints();
}
const int32 Index = GetConstraintsInWorldIndex(InWorld);
if (Index != INDEX_NONE)
{
return ConstraintsInWorld[Index];
}
FConstraintsInWorld& NewConstraintsInWorld = ConstraintsInWorld.Emplace_GetRef();
NewConstraintsInWorld.World = InWorld;
Algo::ForEach(ConstraintsInWorld, [](FConstraintsInWorld& ConstInWorld)
{
return ConstInWorld.InvalidateGraph();
});
return NewConstraintsInWorld;
}
TArray<TWeakObjectPtr<UTickableConstraint>> UConstraintSubsystem::GetConstraints(const UWorld* InWorld) const
{
static const TArray< TWeakObjectPtr<UTickableConstraint> > DummyArray;
if (const FConstraintsInWorld* Constraints = ConstraintsInWorldFind(InWorld))
{
return (Constraints->Constraints);
}
return DummyArray;
}
const TArray<TWeakObjectPtr<UTickableConstraint>>& UConstraintSubsystem::GetConstraintsArray(const UWorld* InWorld) const
{
static const TArray< TWeakObjectPtr<UTickableConstraint> > DummyArray;
if (const FConstraintsInWorld* Constraints = ConstraintsInWorldFind(InWorld))
{
return (Constraints->Constraints);
}
return DummyArray;
}
void UConstraintSubsystem::AddConstraint(UWorld* InWorld, UTickableConstraint* InConstraint)
{
Modify();
FConstraintsInWorld& Constraints = ConstraintsInWorldFindOrAdd(InWorld);
if (Constraints.Constraints.Contains(InConstraint) == false)
{
Constraints.Constraints.Emplace(InConstraint);
Constraints.InvalidateGraph();
}
OnConstraintAddedToSystem_BP.Broadcast(this, InConstraint);
}
void UConstraintSubsystem::RemoveConstraint(UWorld* InWorld, UTickableConstraint* InConstraint, bool bDoNoCompensate)
{
Modify();
OnConstraintRemovedFromSystem_BP.Broadcast(this, InConstraint, bDoNoCompensate);
// disable constraint
InConstraint->Modify();
InConstraint->TeardownConstraint(InWorld);
InConstraint->SetActive(false);
if (FConstraintsInWorld* Constraints = ConstraintsInWorldFind(InWorld))
{
Constraints->Constraints.Remove(InConstraint);
Constraints->InvalidateGraph();
}
}
// we want InFunctionToTickBefore to tick first = InFunctionToTickBefore is a prerex of InFunctionToTickAfter
void UConstraintSubsystem::SetConstraintDependencies(
FConstraintTickFunction* InFunctionToTickBefore,
FConstraintTickFunction* InFunctionToTickAfter)
{
if (!InFunctionToTickBefore || !InFunctionToTickAfter || InFunctionToTickBefore == InFunctionToTickAfter)
{
return;
}
// look for child tick function in in parent's prerequisites.
const TArray<FTickPrerequisite>& ParentPrerequisites = InFunctionToTickAfter->GetPrerequisites();
const bool bIsChildAPrerexOfParent = ParentPrerequisites.ContainsByPredicate([InFunctionToTickBefore](const FTickPrerequisite& Prerex)
{
return Prerex.PrerequisiteTickFunction == InFunctionToTickBefore;
});
// child tick function is already a prerex -> parent already ticks after child
if (bIsChildAPrerexOfParent)
{
return;
}
// look for parent tick function in in child's prerequisites
const TArray<FTickPrerequisite>& ChildPrerequisites = InFunctionToTickBefore->GetPrerequisites();
const bool bIsParentAPrerexOfChild = ChildPrerequisites.ContainsByPredicate([InFunctionToTickAfter](const FTickPrerequisite& Prerex)
{
return Prerex.PrerequisiteTickFunction == InFunctionToTickAfter;
});
// parent tick function is a prerex of the child tick function (child ticks after parent)
// so remove it before setting new dependencies.
if (bIsParentAPrerexOfChild)
{
InFunctionToTickBefore->RemovePrerequisite(this, *InFunctionToTickAfter);
}
// set dependency
InFunctionToTickAfter->AddPrerequisite(this, *InFunctionToTickBefore);
}
bool UConstraintSubsystem::HasConstraint(UWorld* InWorld, UTickableConstraint* InConstraint)
{
const TArray<TWeakObjectPtr<UTickableConstraint>>& Constraints = GetConstraintsArray(InWorld);
return Constraints.Contains(InConstraint);
}
FConstraintsEvaluationGraph& UConstraintSubsystem::GetEvaluationGraph(UWorld* InWorld)
{
return ConstraintsInWorldFindOrAdd(InWorld).GetEvaluationGraph();
}
void UConstraintSubsystem::InvalidateConstraints()
{
bNeedsCleanup = true;
}
#if WITH_EDITOR
void UConstraintSubsystem::PostEditUndo()
{
Super::PostEditUndo();
for (FConstraintsInWorld& ConstsInWorld: ConstraintsInWorld)
{
ConstsInWorld.InvalidateGraph();
}
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(GetWorld());
Controller.Notify(EConstraintsManagerNotifyType::ManagerUpdated, this);
}
#endif
void UConstraintSubsystem::OnWorldInit(UWorld* InWorld, const UWorld::InitializationValues)
{
if (UConstraintSubsystem* System = Get())
{
System->ConstraintsInWorldFindOrAdd(InWorld);
}
}
void UConstraintSubsystem::OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources)
{
if (UConstraintSubsystem* System = Get())
{
const int32 Index = System->GetConstraintsInWorldIndex(InWorld);
if (Index != INDEX_NONE)
{
System->ConstraintsInWorld[Index].RemoveConstraints(InWorld);
System->ConstraintsInWorld.RemoveAt(Index);
Algo::ForEach(System->ConstraintsInWorld, [](FConstraintsInWorld& ConstInWorld)
{
return ConstInWorld.InvalidateGraph();
});
}
}
}
void UConstraintSubsystem::OnPostGarbageCollect()
{
if (UConstraintSubsystem* System = Get())
{
System->InvalidateConstraints();
}
}
void UConstraintSubsystem::CleanupInvalidConstraints() const
{
if (UConstraintSubsystem* System = Get())
{
for (FConstraintsInWorld& WorldConstraints: System->ConstraintsInWorld)
{
UWorld* World = WorldConstraints.World.Get();
TSet<FTickFunction*> ConstraintsTickFunctions;
ConstraintsTickFunctions.Reserve(WorldConstraints.Constraints.Num());
// remove stale constraints and store valid constraints tick functions
WorldConstraints.Constraints.RemoveAll([World, &ConstraintsTickFunctions](const TWeakObjectPtr<UTickableConstraint>& InConstraint)
{
const bool bRemove = !InConstraint.IsValid() || InConstraint.IsStale();
if (!bRemove && World)
{
// store tick functions
ConstraintsTickFunctions.Add(&InConstraint->GetTickFunction(World));
}
return bRemove;
});
if (World)
{
static constexpr bool bEvenIfPendingKill = true;
// cleanup useless tick prerequisites
for (const TWeakObjectPtr<UTickableConstraint>& Constraint : WorldConstraints.Constraints)
{
TArray<FTickPrerequisite>& Prerequisites = Constraint->GetTickFunction(World).GetPrerequisites();
for (int32 PrereqIndex = 0; PrereqIndex < Prerequisites.Num(); PrereqIndex++)
{
FTickPrerequisite& Prerequisite = Prerequisites[PrereqIndex];
UObject* PrereqObject = Prerequisite.PrerequisiteObject.Get(bEvenIfPendingKill);
if (!PrereqObject)
{
// remove prerequisite coming from stale object (cf. FTickFunction::QueueTickFunction)
Prerequisites.RemoveAtSwap(PrereqIndex--);
}
else if (PrereqObject == System && !ConstraintsTickFunctions.Contains(Prerequisite.PrerequisiteTickFunction))
{
// remove prerequisite coming from GCd constraint (cf. UConstraintSubsystem::SetConstraintDependencies)
Prerequisites.RemoveAtSwap(PrereqIndex--);
}
}
}
}
WorldConstraints.InvalidateGraph();
}
bNeedsCleanup = false;
}
}
/*************************************
*FConstraintsInWorld
*************************************/
void FConstraintsInWorld::RemoveConstraints(UWorld* InWorld)
{
for (TWeakObjectPtr<UTickableConstraint>& Constraint : Constraints)
{
if (Constraint.IsValid())
{
Constraint->TeardownConstraint(InWorld);
Constraint->SetActive(false);
}
}
Constraints.SetNum(0);
InvalidateGraph();
}
FConstraintsEvaluationGraph& FConstraintsInWorld::GetEvaluationGraph()
{
if (!EvaluationGraph)
{
EvaluationGraph = MakeShared<FConstraintsEvaluationGraph>(this);
}
return *EvaluationGraph;
}
void FConstraintsInWorld::InvalidateGraph()
{
if (EvaluationGraph)
{
EvaluationGraph.Reset();
}
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(World.Get());
Controller.Notify(EConstraintsManagerNotifyType::GraphUpdated, nullptr);
}