// Copyright Epic Games, Inc. All Rights Reserved. #include "ConstraintsManager.inl" #include "ConstraintsActor.h" #include "ConstraintSubsystem.h" #include "Algo/Copy.h" #include "Algo/StableSort.h" #include "Engine/World.h" #include "Engine/Level.h" #include "Engine/Engine.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ConstraintsManager) /** * FConstraintTickFunction **/ FConstraintTickFunction::FConstraintTickFunction() { TickGroup = TG_PrePhysics; bCanEverTick = true; bStartWithTickEnabled = true; bHighPriority = true; } FConstraintTickFunction::~FConstraintTickFunction() {} FConstraintTickFunction::FConstraintTickFunction(const FConstraintTickFunction& In) : FConstraintTickFunction() { if (In.ConstraintFunctions.Num() > 0) //should only be 1 { ConstraintFunctions.Add(In.ConstraintFunctions[0]); } Constraint = In.Constraint; } void FConstraintTickFunction::ExecuteTick( float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { EvaluateFunctions(); } void FConstraintTickFunction::RegisterFunction(ConstraintFunction InConstraint) { if (ConstraintFunctions.IsEmpty()) { ConstraintFunctions.Add(InConstraint); } } void FConstraintTickFunction::EvaluateFunctions() const { for (const ConstraintFunction& Function: ConstraintFunctions) { Function(); } } FString FConstraintTickFunction::DiagnosticMessage() { if(!Constraint.IsValid()) { return FString::Printf(TEXT("FConstraintTickFunction::Tick[%p]"), this); } #if WITH_EDITOR return FString::Printf(TEXT("FConstraintTickFunction::Tick[%p] (%s)"), this, *Constraint->GetLabel()); #else return FString::Printf(TEXT("FConstraintTickFunction::Tick[%p] (%s)"), this, *Constraint->GetName()); #endif } /** * UTickableConstraint **/ FConstraintTickFunction& UTickableConstraint::GetTickFunction(UWorld* InWorld) { FConstraintTickFunction& ConstraintTick = ConstraintTicks.FindOrAdd(InWorld->GetCurrentLevel()); return ConstraintTick; } const FConstraintTickFunction& UTickableConstraint::GetTickFunction(UWorld* InWorld) const { const FConstraintTickFunction& ConstraintTick = ConstraintTicks.FindOrAdd(InWorld->GetCurrentLevel()); return ConstraintTick; } void UTickableConstraint::SetActive(const bool bIsActive) { Active = bIsActive; for (TPair, FConstraintTickFunction>& Pair : ConstraintTicks) { if (Pair.Key.IsValid()) { Pair.Value.SetTickFunctionEnable(bIsActive); } } } bool UTickableConstraint::IsFullyActive() const { return Active; } void UTickableConstraint::Evaluate(bool bTickHandlesAlso) const { for (const TPair, FConstraintTickFunction>& Pair : ConstraintTicks) { if (Pair.Key.IsValid()) { Pair.Value.EvaluateFunctions(); } } } UTickableConstraint* UTickableConstraint::Duplicate(UObject* NewOuter) const { return DuplicateObject(this, NewOuter, GetFName()); } #if WITH_EDITOR FString UTickableConstraint::GetLabel() const { return UTickableConstraint::StaticClass()->GetName(); } FString UTickableConstraint::GetFullLabel() const { return GetLabel(); } FString UTickableConstraint::GetTypeLabel() const { return GetLabel(); } void UTickableConstraint::PostDuplicate(bool bDuplicateForPIE) { Super::PostDuplicate(bDuplicateForPIE); if (bDuplicateForPIE == false) //doing actually copy then give unique id { ConstraintID = FGuid::NewGuid(); } } void UTickableConstraint::PostLoad() { Super::PostLoad(); if (ConstraintID.IsValid() == false) { ConstraintID = FGuid::NewGuid(); } } void UTickableConstraint::PostInitProperties() { Super::PostInitProperties(); if (HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad) == false) { ConstraintID = FGuid::NewGuid(); } } void UTickableConstraint::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); const FName PropertyName = PropertyChangedEvent.GetPropertyName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UTickableConstraint, Active)) { for (TPair, FConstraintTickFunction>& Pair : ConstraintTicks) { if (Pair.Key.IsValid()) { Pair.Value.SetTickFunctionEnable(Active); if (Active) { Pair.Value.EvaluateFunctions(); } } } } } void UTickableConstraint::PostEditUndo() { Super::PostEditUndo(); // make sure we update ticking when undone const bool bActiveTick = ::IsValidChecked(this); for (TPair, FConstraintTickFunction>& Pair : ConstraintTicks) { if (Pair.Key.IsValid()) { Pair.Value.SetTickFunctionEnable(bActiveTick); Pair.Value.bCanEverTick = bActiveTick; } } } #endif /** * UConstraintsManager **/ UConstraintsManager::UConstraintsManager() {} UConstraintsManager::~UConstraintsManager() { } void UConstraintsManager::PostLoad() { Super::PostLoad(); for (TObjectPtr& ConstPtr : Constraints) { if (ConstPtr) { ConstPtr->InitConstraint(GetWorld()); } } } void UConstraintsManager::OnActorDestroyed(AActor* InActor) { if (USceneComponent* SceneComponent = InActor->GetRootComponent()) { TArray< int32 > IndicesToRemove; for (int32 Index = 0; Index < Constraints.Num(); ++Index) { //if the constraint has a bound object(in sequencer) we don't remove the constraint, it could be a spawnable if (Constraints[Index] && IsValid(Constraints[Index].Get()) && Constraints[Index]->HasBoundObjects() == false && Constraints[Index]->ReferencesObject(SceneComponent)) { IndicesToRemove.Add(Index); } } if (!IndicesToRemove.IsEmpty()) { FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InActor->GetWorld()); if (FConstraintsManagerController::bDoNotRemoveConstraint == false) { for (int32 Index = IndicesToRemove.Num() - 1; Index >= 0; --Index) { Controller.RemoveConstraint(Index,/*do not compensate*/ true); } } } } } void UConstraintsManager::RegisterDelegates(UWorld* World) { if (!OnActorDestroyedHandle.IsValid()) { FOnActorDestroyed::FDelegate ActorDestroyedDelegate = FOnActorDestroyed::FDelegate::CreateUObject(this, &UConstraintsManager::OnActorDestroyed); OnActorDestroyedHandle = World->AddOnActorDestroyedHandler(ActorDestroyedDelegate); } } void UConstraintsManager::UnregisterDelegates(UWorld* World) { if (World) { World->RemoveOnActorSpawnedHandler(OnActorDestroyedHandle); } OnActorDestroyedHandle.Reset(); } void UConstraintsManager::Init(UWorld* World) { if (World) { UnregisterDelegates(World); RegisterDelegates(World); } } UConstraintsManager* UConstraintsManager::Get(UWorld* InWorld) { if (!IsValid(InWorld)) { return nullptr; } // look for ConstraintsActor and return its manager if (UConstraintsManager* Manager = Find(InWorld)) { return Manager; } // create a new ConstraintsActor AConstraintsActor* ConstraintsActor = InWorld->SpawnActor(); #if WITH_EDITOR ConstraintsActor->SetActorLabel(TEXT("Constraints Manager")); #endif // WITH_EDITOR return ConstraintsActor->ConstraintsManager; } UConstraintsManager* UConstraintsManager::Find(const UWorld* InWorld) { if (!IsValid(InWorld)) { return nullptr; } // should we work with the persistent level? const ULevel* Level = InWorld->GetCurrentLevel(); // look for first ConstraintsActor auto FindFirstConstraintsActor = [](const ULevel* Level) { const int32 Index = Level->Actors.IndexOfByPredicate([](const AActor* Actor) { return IsValid(Actor) && Actor->IsA(); } ); return Index != INDEX_NONE ? Cast(Level->Actors[Index]) : nullptr; }; const AConstraintsActor* ConstraintsActor = FindFirstConstraintsActor(Level); return ConstraintsActor ? ConstraintsActor->ConstraintsManager : nullptr; } void UConstraintsManager::Clear(UWorld* InWorld) { UnregisterDelegates(InWorld); } void UConstraintsManager::Dump() const { UE_LOG(LogTemp, Error, TEXT("nb consts = %d"), Constraints.Num()); for (const TObjectPtr& Constraint: Constraints) { if (IsValid(Constraint)) { UE_LOG(LogTemp, Warning, TEXT("\t%s (target hash = %u)"), *Constraint->GetName(), Constraint->GetTargetHash()); } } } /** * FConstraintsManagerController **/ bool FConstraintsManagerController::bDoNotRemoveConstraint = false; FConstraintsManagerController& FConstraintsManagerController::Get(UWorld* InWorld) { static FConstraintsManagerController Singleton; Singleton.World = InWorld; return Singleton; } UConstraintsManager* FConstraintsManagerController::GetManager() { UConstraintsManager* Manager = FindManager(); if (Manager) { return Manager; } // create a new ConstraintsActor AConstraintsActor* ConstraintsActor = World->SpawnActor(); #if WITH_EDITOR ConstraintsActor->SetActorLabel(TEXT("Constraints Manager")); #endif // WITH_EDITOR return ConstraintsActor->ConstraintsManager; } UConstraintsManager* FConstraintsManagerController::FindManager() const { if (!World) { return nullptr; } // should we work with the persistent level? const ULevel* Level = World->GetCurrentLevel(); // look for first ConstraintsActor auto FindFirstConstraintsActor = [](const ULevel* Level) { const int32 Index = Level->Actors.IndexOfByPredicate([](const AActor* Actor) { return IsValid(Actor) && Actor->IsA(); } ); return Index != INDEX_NONE ? Cast(Level->Actors[Index]) : nullptr; }; const AConstraintsActor* ConstraintsActor = FindFirstConstraintsActor(Level); return ConstraintsActor ? ConstraintsActor->ConstraintsManager : nullptr; } void FConstraintsManagerController::DestroyManager() const { if (!World) { return; } // should we work with the persistent level? const ULevel* Level = World->GetCurrentLevel(); // note there should be only one... TArray ConstraintsActorsToRemove; Algo::CopyIf(Level->Actors, ConstraintsActorsToRemove, [](const AActor* Actor) { return IsValid(Actor) && Actor->IsA(); } ); for (AActor* ConstraintsActor: ConstraintsActorsToRemove) { World->DestroyActor(ConstraintsActor, true); } } void FConstraintsManagerController::StaticConstraintCreated(UWorld* InWorld, UTickableConstraint* InConstraint) { if (UConstraintsManager* Manager = GetManager()) //created If needed { if (Manager->Constraints.Contains(InConstraint) == false) { Manager->Modify(); Manager->Constraints.Add(InConstraint); InConstraint->Rename(nullptr, Manager, REN_DontCreateRedirectors); Manager->OnConstraintAdded_BP.Broadcast(Manager, InConstraint); } } } bool FConstraintsManagerController::AddConstraint(UTickableConstraint* InConstraint) const { if (!InConstraint) { return false; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return false; } if (Subsystem->HasConstraint(World, InConstraint) == true) { return false; } InConstraint->InitConstraint(World); Subsystem->AddConstraint(World,InConstraint); // build dependencies InConstraint->AddedToWorld(World); // notify Notify(EConstraintsManagerNotifyType::ConstraintAdded, InConstraint); return true; } UTickableConstraint* FConstraintsManagerController::AddConstraintFromCopy(UTickableConstraint* CopyOfConstraint) const { if (!CopyOfConstraint) { return nullptr; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return nullptr; } int32 Index = GetConstraintIndex(CopyOfConstraint->ConstraintID); if (Index != INDEX_NONE) { const TArray>& Constraints = Subsystem->GetConstraintsArray(World); return Constraints[Index].Get(); } UObject* Outer = CopyOfConstraint->GetOuter(); UTickableConstraint* OurCopy = CopyOfConstraint->Duplicate(Outer); OurCopy->ConstraintID = CopyOfConstraint->ConstraintID; OurCopy->SetActive(false); //always set false if (AddConstraint(OurCopy)) { return OurCopy; } return nullptr; } int32 FConstraintsManagerController::GetConstraintIndex(const FGuid& InGuid) const { const UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return INDEX_NONE; } const TArray> Constraints = Subsystem->GetConstraints(World); return Constraints.IndexOfByPredicate([InGuid](const TWeakObjectPtr& Constraint) { return (Constraint.IsValid() && Constraint->ConstraintID == InGuid); }); } int32 FConstraintsManagerController::GetConstraintIndex(const FName& InConstraintName) const { const UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return INDEX_NONE; } const TArray> Constraints = Subsystem->GetConstraints(World); return Constraints.IndexOfByPredicate([InConstraintName](const TWeakObjectPtr& Constraint) { return (Constraint.IsValid() && Constraint->GetFName() == InConstraintName); } ); } bool FConstraintsManagerController::RemoveConstraint(UTickableConstraint* InConstraint, bool bDoNotCompensate) { if (FConstraintsManagerController::bDoNotRemoveConstraint == true) { return false; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return false; } TArray> Constraints = Subsystem->GetConstraints(World); int32 Index = Constraints.IndexOfByPredicate([InConstraint](const TWeakObjectPtr& Constraint) { return (Constraint.IsValid() && Constraint.Get() == InConstraint); }); if (Index == INDEX_NONE) { return false; } return RemoveConstraint(Index, bDoNotCompensate); } bool FConstraintsManagerController::UnregisterConstraint(UTickableConstraint* InConstraint) { if (!InConstraint) { return false; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return false; } static constexpr bool bDoNotCompensate = true; if (Subsystem->HasConstraint(World, InConstraint)) { Subsystem->RemoveConstraint(World, InConstraint, bDoNotCompensate); return true; } return false; } bool FConstraintsManagerController::RemoveConstraint(const int32 InConstraintIndex,bool bDoNotCompensate) { UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return false; } TArray> Constraints = Subsystem->GetConstraints(World); const FName ConstraintName = Constraints[InConstraintIndex]->GetFName(); UTickableConstraint* Constraint = Constraints[InConstraintIndex].Get(); // notify deletion auto GetRemoveNotifyType = [bDoNotCompensate]() { if(bDoNotCompensate) { return EConstraintsManagerNotifyType::ConstraintRemoved; } return EConstraintsManagerNotifyType::ConstraintRemovedWithCompensation; }; Notify(GetRemoveNotifyType(), Constraint); Subsystem->Modify(); Subsystem->RemoveConstraint(World, Constraint, bDoNotCompensate); if (UConstraintsManager* Manager = FindManager()) { if (Manager->Constraints.Contains(Constraint) == true) { Manager->Modify(); Manager->Constraints.Remove(Constraint); Manager->OnConstraintRemoved_BP.Broadcast(Manager, Constraint, bDoNotCompensate); // destroy constraints actor if no constraints left if (Manager->Constraints.IsEmpty()) { DestroyManager(); } } } return true; } UTickableConstraint* FConstraintsManagerController::GetConstraint(const FGuid& InGuid) const { const int32 Index = GetConstraintIndex(InGuid); if (Index == INDEX_NONE) { return nullptr; } return GetConstraint(Index); } UTickableConstraint* FConstraintsManagerController::GetConstraint(const int32 InConstraintIndex) const { UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return nullptr; } TArray> Constraints = Subsystem->GetConstraints(World); if (!Constraints.IsValidIndex(InConstraintIndex)) { return nullptr; } return Constraints[InConstraintIndex].Get(); } TArray< TWeakObjectPtr > FConstraintsManagerController::GetParentConstraints( const uint32 InTargetHash, const bool bSorted) const { static const TArray< TWeakObjectPtr > DummyArray; if (InTargetHash == 0) { return DummyArray; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return DummyArray; } TArray> Constraints = Subsystem->GetConstraints(World); using ConstraintPtr = TWeakObjectPtr; TArray< TWeakObjectPtr > FilteredConstraints = Constraints.FilterByPredicate( [InTargetHash](const ConstraintPtr& Constraint) { return Constraint.IsValid() && IsValid(Constraint.Get()) && Constraint->GetTargetHash() == InTargetHash; } ); if (bSorted) { // LHS ticks before RHS = LHS is a prerex of RHS auto TicksBefore = [this](const UTickableConstraint& LHS, const UTickableConstraint& RHS) { const TArray& RHSPrerex = RHS.GetTickFunction(World).GetPrerequisites(); const FConstraintTickFunction& LHSTickFunction = LHS.GetTickFunction(World); const bool bIsLHSAPrerexOfRHS = RHSPrerex.ContainsByPredicate([&LHSTickFunction](const FTickPrerequisite& Prerex) { return Prerex.PrerequisiteTickFunction == &LHSTickFunction; }); return bIsLHSAPrerexOfRHS; }; Algo::StableSort(FilteredConstraints, [TicksBefore](const ConstraintPtr& LHS, const ConstraintPtr& RHS) { return TicksBefore(*LHS, *RHS); }); } return FilteredConstraints; } void FConstraintsManagerController::SetConstraintsDependencies( const FName& InNameToTickBefore, const FName& InNameToTickAfter) const { UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return; } const TArray> Constraints = Subsystem->GetConstraints(World); const int32 IndexBefore = GetConstraintIndex(InNameToTickBefore); const int32 IndexAfter = GetConstraintIndex(InNameToTickAfter); if (IndexBefore == INDEX_NONE || IndexAfter == INDEX_NONE || IndexAfter == IndexBefore) { return; } FConstraintTickFunction& FunctionToTickBefore = Constraints[IndexBefore]->GetTickFunction(World); FConstraintTickFunction& FunctionToTickAfter = Constraints[IndexAfter]->GetTickFunction(World); Subsystem->SetConstraintDependencies(&FunctionToTickBefore, &FunctionToTickAfter); InvalidateEvaluationGraph(); } void FConstraintsManagerController::SetConstraintsDependencies(const struct FGuid& InGuidToTickBefore, const struct FGuid& InGuidToTickAfter) const { if (!InGuidToTickBefore.IsValid() || !InGuidToTickAfter.IsValid() || InGuidToTickBefore == InGuidToTickAfter) { return; } UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { return; } const TArray>& Constraints = Subsystem->GetConstraintsArray(World); const int32 IndexBefore = Constraints.IndexOfByPredicate([InGuidToTickBefore](const TWeakObjectPtr& Constraint) { return Constraint.IsValid() && Constraint->ConstraintID == InGuidToTickBefore; }); const int32 IndexAfter = Constraints.IndexOfByPredicate([InGuidToTickAfter](const TWeakObjectPtr& Constraint) { return Constraint.IsValid() && Constraint->ConstraintID == InGuidToTickAfter; }); if (IndexBefore == INDEX_NONE || IndexAfter == INDEX_NONE || IndexAfter == IndexBefore) { return; } FConstraintTickFunction& FunctionToTickBefore = Constraints[IndexBefore]->GetTickFunction(World); FConstraintTickFunction& FunctionToTickAfter = Constraints[IndexAfter]->GetTickFunction(World); Subsystem->SetConstraintDependencies(&FunctionToTickBefore, &FunctionToTickAfter); InvalidateEvaluationGraph(); } const TArray< TWeakObjectPtr >& FConstraintsManagerController::GetConstraintsArray() const { UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { static const TArray< TWeakObjectPtr > Empty; return Empty; } const TArray>& Constraints = Subsystem->GetConstraintsArray(World); return Constraints; } bool FConstraintsManagerController::RemoveAllConstraints(bool bDoNotCompensate) { if (UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get()) { Subsystem->Modify(); TArray> Constraints = Subsystem->GetConstraints(World); for (TWeakObjectPtr& Constraint : Constraints) { RemoveConstraint(Constraint.Get(), bDoNotCompensate); } } return false; } TArray< TWeakObjectPtr > FConstraintsManagerController::GetStaticConstraints(const bool bSorted) const { UConstraintsManager* Manager = FindManager(); if (!Manager) { static const TArray< TWeakObjectPtr > Empty; return Empty; } // Remove stale constraints. Stale constraints may be caused by unexpected unloading of contributing objects, such as a level sequence. Manager->Constraints.RemoveAll([](const TObjectPtr& ExistingConstraint) -> bool { return !IsValid(ExistingConstraint); }); TArray< TWeakObjectPtr > Constraints(Manager->Constraints); if (!bSorted) { return Constraints; } UE::Constraints::Graph::SortConstraints(World, Constraints); return Constraints; } TArray< TWeakObjectPtr > FConstraintsManagerController::GetAllConstraints(const bool bSorted) const { UConstraintSubsystem* Subsystem = UConstraintSubsystem::Get(); if (!Subsystem) { static const TArray< TWeakObjectPtr > Empty; return Empty; } // use evaluation graph cached data if (FConstraintsEvaluationGraph::UseEvaluationGraph() && bSorted) { TArray> SortedConstraints; if (Subsystem->GetEvaluationGraph(World).GetSortedConstraints(SortedConstraints)) { return SortedConstraints; } } TArray> Constraints = Subsystem->GetConstraints(World); // Remove stale constraints (note that GCd constraints should already have been caught by UConstraintSubsystem) Constraints.RemoveAll([](const TWeakObjectPtr& ExistingConstraint) -> bool { return ExistingConstraint.IsStale(); }); if (!bSorted) { return Constraints; } UE::Constraints::Graph::SortConstraints(World, Constraints); return Constraints; } void FConstraintsManagerController::EvaluateAllConstraints() const { using ConstraintPtr = TWeakObjectPtr; static constexpr bool bSorted = true, bTickHandles = true; const TArray Constraints = GetAllConstraints(bSorted); for (const ConstraintPtr& Constraint : Constraints) { if (Constraint.IsValid()) { Constraint->Evaluate(bTickHandles); } } } void FConstraintsManagerController::Notify(EConstraintsManagerNotifyType InNotifyType, UObject* InObject) const { switch (InNotifyType) { case EConstraintsManagerNotifyType::ConstraintAdded: case EConstraintsManagerNotifyType::ConstraintRemoved: case EConstraintsManagerNotifyType::ConstraintRemovedWithCompensation: checkSlow(Cast(InObject) != nullptr); break; case EConstraintsManagerNotifyType::ManagerUpdated: checkSlow(Cast(InObject) != nullptr); break; case EConstraintsManagerNotifyType::GraphUpdated: break; default: checkfSlow(false, TEXT("Unchecked EConstraintsManagerNotifyType!")); break; } NotifyDelegate.Broadcast(InNotifyType, InObject); } void FConstraintsManagerController::MarkConstraintForEvaluation(UTickableConstraint* InConstraint) const { UConstraintSubsystem* SubSystem = UConstraintSubsystem::Get(); if (!SubSystem) { return; } SubSystem->GetEvaluationGraph(World).MarkForEvaluation(InConstraint); } void FConstraintsManagerController::InvalidateEvaluationGraph() const { UConstraintSubsystem* SubSystem = UConstraintSubsystem::Get(); if (!SubSystem) { return; } SubSystem->GetEvaluationGraph(World).InvalidateData(); Notify(EConstraintsManagerNotifyType::GraphUpdated, nullptr); } void FConstraintsManagerController::FlushEvaluationGraph() const { if (!FConstraintsEvaluationGraph::UseEvaluationGraph()) { return; } UConstraintSubsystem* SubSystem = UConstraintSubsystem::Get(); if (!SubSystem) { return; } FConstraintsEvaluationGraph& EvaluationGraph = SubSystem->GetEvaluationGraph(World); if (EvaluationGraph.IsPendingEvaluation()) { EvaluationGraph.FlushPendingEvaluations(); } } bool FConstraintsManagerController::DoesExistInAnyWorld(UTickableConstraint* InConstraint) { bool bFound = false; if (InConstraint) { if (UConstraintSubsystem * Subsystem = UConstraintSubsystem::Get()) { UWorld* CurrentWorld = World; //save current World for (const FWorldContext& Context : GEngine->GetWorldContexts()) { World = Context.World(); if (Subsystem->GetConstraintsArray(World).Find(InConstraint) != INDEX_NONE) { bFound = true; break; } } World = CurrentWorld; //restore current World } } return bFound; }