// 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