Files
BXSSP_Andriod/Plugins/CesiumForUnreal/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h

218 lines
7.2 KiB
C++

// 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();
/// <summary>
/// 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.
/// </summary>
void TestRotatorsAreEquivalent(
FAutomationTestBase* pSpec,
ACesiumGeoreference* pGeoreferenceExpected,
const FRotator& rotatorExpected,
ACesiumGeoreference* pGeoreferenceActual,
const FRotator& rotatorActual);
template <typename T>
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<T>(condition),
timerHandle]() mutable {
waitForImpl<T>(done, pWorld, std::move(condition), timerHandle);
});
}
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="done">The done delegate provided by a LatentIt or
/// LatentBeforeEach. It will be invoked when the condition is true or when the
/// timeout elapses.</param>
/// <param name="pWorld">The world in which to check the condition.</param>
/// <param name="timeoutSeconds">The maximum time to wait for the condition to
/// become true.</param>
/// <param name="condition">A lambda that is invoked each
/// frame. If this function returns false, waiting continues.</param>
template <typename T>
void waitFor(
const FDoneDelegate& done,
UWorld* pWorld,
float timeoutSeconds,
T&& condition) {
FTimerHandle timerHandle;
pWorld->GetTimerManager().SetTimer(timerHandle, timeoutSeconds, false);
waitForImpl<T>(done, pWorld, std::forward<T>(condition), timerHandle);
}
void waitForNextFrame(
const FDoneDelegate& done,
UWorld* pWorld,
float timeoutSeconds);
/// <summary>
/// Gets the first Actor that has a given tag.
/// </summary>
/// <typeparam name="T">The Actor type.</typeparam>
/// <param name="pWorld">The world in which to search for the Actor.</param>
/// <param name="tag">The tag to look for.</param>
/// <returns>The Actor, or nullptr if it does not exist.</returns>
template <typename T> T* getActorWithTag(UWorld* pWorld, const FName& tag) {
TArray<AActor*> temp;
UGameplayStatics::GetAllActorsWithTag(pWorld, tag, temp);
if (temp.Num() < 1)
return nullptr;
return Cast<T>(temp[0]);
}
/// <summary>
/// Gets the first ActorComponent that has a given tag.
/// </summary>
/// <typeparam name="T">The ActorComponent type.</typeparam>
/// <param name="pOwner">The Actor whose components to search.</param>
/// <param name="tag">The tag to look for.</param>
/// <returns>The ActorComponent, or nullptr if it does not exist.</returns>
template <typename T> T* getComponentWithTag(AActor* pOwner, const FName& tag) {
TArray<UActorComponent*> components =
pOwner->GetComponentsByTag(T::StaticClass(), tag);
if (components.Num() < 1)
return nullptr;
return Cast<T>(components[0]);
}
/// <summary>
/// Gets a tag that can be used to uniquely identify a given Actor.
/// </summary>
/// <param name="pActor">The actor.</param>
/// <returns>The unique tag.</returns>
FName getUniqueTag(AActor* pActor);
/// <summary>
/// Gets a tag that can be used to uniquely identify a given ActorComponent.
/// </summary>
/// <param name="pActor">The actor.</param>
/// <returns>The unique tag.</returns>
FName getUniqueTag(UActorComponent* pComponent);
/// <summary>
/// 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.
/// </summary>
void pushAllowTickInEditor();
/// <summary>
/// Call this after a test that needs working ticking to restore the default
/// state.
/// </summary>
void popAllowTickInEditor();
#if WITH_EDITOR
/// <summary>
/// Tracks a provided Edit-mode Actor, so the equivalent object can later be
/// found in Play mode.
/// </summary>
/// <param name="pEditorActor">The Actor in the Editor world to track.</param>
void trackForPlay(AActor* pEditorActor);
/// <summary>
/// Tracks a provided Edit-mode ActorComponent, so the equivalent object can
/// later be found in Play mode.
/// </summary>
/// <param name="pEditorComponent">The ActorComponent in the Editor world to
/// track.</param>
void trackForPlay(UActorComponent* pEditorComponent);
/// <summary>
/// Finds a Play-mode object equivalent to a given Editor-mode one that was
/// previously tracked with <see cref="TrackForPlay" />. This works on instances
/// derived from AActor and UActorComponent.
/// </summary>
/// <typeparam name="T">The type of the object to find.</typeparam>
/// <param name="pEditorObject">The Editor object for which to find a Play-mode
/// equivalent.</param>
/// <returns>The play mode equivalent, or nullptr is one is not found.</returns>
template <typename T> T* findInPlay(T* pEditorObject) {
if (!IsValid(pEditorObject))
return nullptr;
UWorld* pWorld = GEditor->PlayWorld;
if constexpr (std::is_base_of_v<AActor, T>) {
return getActorWithTag<T>(pWorld, getUniqueTag(pEditorObject));
} else if constexpr (std::is_base_of_v<UActorComponent, T>) {
AActor* pEditorOwner = pEditorObject->GetOwner();
if (!pEditorOwner)
return nullptr;
AActor* pPlayOwner = findInPlay(pEditorOwner);
if (!pPlayOwner)
return nullptr;
return getComponentWithTag<T>(pPlayOwner, getUniqueTag(pEditorObject));
} else {
return nullptr;
}
}
/// <summary>
/// Finds a Play-mode object equivalent to a given Editor-mode one that was
/// previously tracked with <see cref="TrackForPlay" />. This works on instances
/// derived from AActor and UActorComponent.
/// </summary>
/// <typeparam name="T">The type of the object to find.</typeparam>
/// <param name="pEditorObject">The Editor object for which to find a Play-mode
/// equivalent.</param>
/// <returns>The play mode equivalent, or nullptr is one is
/// not found.</returns>
template <typename T> T* findInPlay(TObjectPtr<T> pEditorObject) {
return findInPlay<T>(pEditorObject.Get());
}
#endif // #if WITH_EDITOR
} // namespace CesiumTestHelpers