// Copyright 2020-2024 CesiumGS, Inc. and Contributors #pragma once #include "CesiumRuntime.h" #include "EngineUtils.h" #include "Kismet/GameplayStatics.h" #include "Math/MathFwd.h" #include "Misc/AutomationTest.h" #include "TimerManager.h" #if WITH_EDITOR #include "Editor.h" #endif class UWorld; class ACesiumGeoreference; namespace CesiumTestHelpers { UWorld* getGlobalWorldContext(); /// /// Verify that two rotations (expressed relative to two different /// georeferences) are equivalent by using them to rotate the principal axis /// vectors and then transforming those vectors to ECEF. The ECEF vectors should /// be the same in both cases. /// void TestRotatorsAreEquivalent( FAutomationTestBase* pSpec, ACesiumGeoreference* pGeoreferenceExpected, const FRotator& rotatorExpected, ACesiumGeoreference* pGeoreferenceActual, const FRotator& rotatorActual); template void waitForImpl( const FDoneDelegate& done, UWorld* pWorld, T&& condition, FTimerHandle& timerHandle) { if (condition()) { pWorld->GetTimerManager().ClearTimer(timerHandle); done.Execute(); } else if (pWorld->GetTimerManager().GetTimerRemaining(timerHandle) <= 0.0f) { // Timeout UE_LOG( LogCesium, Error, TEXT("Timed out waiting for a condition to become true.")); pWorld->GetTimerManager().ClearTimer(timerHandle); done.Execute(); } else { pWorld->GetTimerManager().SetTimerForNextTick( [done, pWorld, condition = std::forward(condition), timerHandle]() mutable { waitForImpl(done, pWorld, std::move(condition), timerHandle); }); } } /// /// Waits for a provided lambda function to become true, ticking through render /// frames in the meantime. If the timeout elapses before the condition becomes /// true, an error is logged (which will cause a test failure) and the done /// delegate is invoked anyway. /// /// /// The done delegate provided by a LatentIt or /// LatentBeforeEach. It will be invoked when the condition is true or when the /// timeout elapses. /// The world in which to check the condition. /// The maximum time to wait for the condition to /// become true. /// A lambda that is invoked each /// frame. If this function returns false, waiting continues. template void waitFor( const FDoneDelegate& done, UWorld* pWorld, float timeoutSeconds, T&& condition) { FTimerHandle timerHandle; pWorld->GetTimerManager().SetTimer(timerHandle, timeoutSeconds, false); waitForImpl(done, pWorld, std::forward(condition), timerHandle); } void waitForNextFrame( const FDoneDelegate& done, UWorld* pWorld, float timeoutSeconds); /// /// Gets the first Actor that has a given tag. /// /// The Actor type. /// The world in which to search for the Actor. /// The tag to look for. /// The Actor, or nullptr if it does not exist. template T* getActorWithTag(UWorld* pWorld, const FName& tag) { TArray temp; UGameplayStatics::GetAllActorsWithTag(pWorld, tag, temp); if (temp.Num() < 1) return nullptr; return Cast(temp[0]); } /// /// Gets the first ActorComponent that has a given tag. /// /// The ActorComponent type. /// The Actor whose components to search. /// The tag to look for. /// The ActorComponent, or nullptr if it does not exist. template T* getComponentWithTag(AActor* pOwner, const FName& tag) { TArray components = pOwner->GetComponentsByTag(T::StaticClass(), tag); if (components.Num() < 1) return nullptr; return Cast(components[0]); } /// /// Gets a tag that can be used to uniquely identify a given Actor. /// /// The actor. /// The unique tag. FName getUniqueTag(AActor* pActor); /// /// Gets a tag that can be used to uniquely identify a given ActorComponent. /// /// The actor. /// The unique tag. FName getUniqueTag(UActorComponent* pComponent); /// /// By default, UE 5.3+ don't tick in a headless Editor, which is often used to /// run tests. Call this at the start of a test that requires ticking to /// override this default. Call popAllowTickInEditor after the test to restore /// the default. /// void pushAllowTickInEditor(); /// /// Call this after a test that needs working ticking to restore the default /// state. /// void popAllowTickInEditor(); #if WITH_EDITOR /// /// Tracks a provided Edit-mode Actor, so the equivalent object can later be /// found in Play mode. /// /// The Actor in the Editor world to track. void trackForPlay(AActor* pEditorActor); /// /// Tracks a provided Edit-mode ActorComponent, so the equivalent object can /// later be found in Play mode. /// /// The ActorComponent in the Editor world to /// track. void trackForPlay(UActorComponent* pEditorComponent); /// /// Finds a Play-mode object equivalent to a given Editor-mode one that was /// previously tracked with . This works on instances /// derived from AActor and UActorComponent. /// /// The type of the object to find. /// The Editor object for which to find a Play-mode /// equivalent. /// The play mode equivalent, or nullptr is one is not found. template T* findInPlay(T* pEditorObject) { if (!IsValid(pEditorObject)) return nullptr; UWorld* pWorld = GEditor->PlayWorld; if constexpr (std::is_base_of_v) { return getActorWithTag(pWorld, getUniqueTag(pEditorObject)); } else if constexpr (std::is_base_of_v) { AActor* pEditorOwner = pEditorObject->GetOwner(); if (!pEditorOwner) return nullptr; AActor* pPlayOwner = findInPlay(pEditorOwner); if (!pPlayOwner) return nullptr; return getComponentWithTag(pPlayOwner, getUniqueTag(pEditorObject)); } else { return nullptr; } } /// /// Finds a Play-mode object equivalent to a given Editor-mode one that was /// previously tracked with . This works on instances /// derived from AActor and UActorComponent. /// /// The type of the object to find. /// The Editor object for which to find a Play-mode /// equivalent. /// The play mode equivalent, or nullptr is one is /// not found. template T* findInPlay(TObjectPtr pEditorObject) { return findInPlay(pEditorObject.Get()); } #endif // #if WITH_EDITOR } // namespace CesiumTestHelpers