2026-07-15 19:17:47 +02:00
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
# include "Tasks/DirectiveUtilTask_AsyncTrace.h"
# include "Tasks/DirectiveUtilTask_MoveToLocation.h"
# include "Tests/DirectiveUtilTestObject.h"
# include "Engine/World.h"
# include "Engine/Engine.h"
# include "Engine/EngineBaseTypes.h"
# include "Engine/EngineTypes.h"
# include "Engine/StaticMesh.h"
# include "Engine/StaticMeshActor.h"
# include "Components/StaticMeshComponent.h"
# include "GameFramework/DefaultPawn.h"
# include "GameFramework/PlayerController.h"
2026-08-15 21:06:43 +02:00
# include "TimerManager.h"
2026-07-15 19:17:47 +02:00
# include "Misc/AutomationTest.h"
namespace DirectiveUtilAsyncTraceTestHelpers
{
UWorld * CreateTraceWorld ( )
{
UWorld * World = UWorld : : CreateWorld ( EWorldType : : Game , false ) ;
if ( ! World )
{
return nullptr ;
}
FWorldContext & WorldContext = GEngine - > CreateNewWorldContext ( EWorldType : : Game ) ;
WorldContext . SetCurrentWorld ( World ) ;
World - > InitializeActorsForPlay ( FURL ( ) ) ;
World - > BeginPlay ( ) ;
return World ;
}
AStaticMeshActor * SpawnBlockingCube ( UWorld * World , UStaticMesh * CubeMesh )
{
AStaticMeshActor * Cube = World - > SpawnActor < AStaticMeshActor > ( FVector : : ZeroVector , FRotator : : ZeroRotator ) ;
UStaticMeshComponent * Component = Cube - > GetStaticMeshComponent ( ) ;
Component - > SetMobility ( EComponentMobility : : Movable ) ;
Component - > SetStaticMesh ( CubeMesh ) ;
Component - > SetCollisionProfileName ( TEXT ( " BlockAll " ) ) ;
Component - > SetCollisionEnabled ( ECollisionEnabled : : QueryAndPhysics ) ;
return Cube ;
}
}
2026-08-15 21:06:43 +02:00
bool UDirectiveUtilTestMoveToLocationTask : : HasRegisteredTimers ( ) const
{
const UWorld * World = TimerWorld . Get ( ) ;
return World & & ( World - > GetTimerManager ( ) . TimerExists ( TimerHandle ) | | World - > GetTimerManager ( ) . TimerExists ( StuckTimerHandle ) ) ;
}
void UDirectiveUtilTestMoveToLocationTask : : RegisterTimersForTest ( UWorld * World )
{
TimerWorld = World ;
World - > GetTimerManager ( ) . SetTimer ( TimerHandle , FTimerDelegate : : CreateLambda ( [ ] { } ) , 60.0f , true ) ;
World - > GetTimerManager ( ) . SetTimer ( StuckTimerHandle , FTimerDelegate : : CreateLambda ( [ ] { } ) , 60.0f , true ) ;
}
bool UDirectiveUtilTestMoveToActorTask : : HasRegisteredTimers ( ) const
{
const UWorld * World = TimerWorld . Get ( ) ;
return World & & ( World - > GetTimerManager ( ) . TimerExists ( TimerHandle ) | | World - > GetTimerManager ( ) . TimerExists ( StuckTimerHandle ) ) ;
}
void UDirectiveUtilTestMoveToActorTask : : RegisterTimersForTest ( UWorld * World )
{
TimerWorld = World ;
World - > GetTimerManager ( ) . SetTimer ( TimerHandle , FTimerDelegate : : CreateLambda ( [ ] { } ) , 60.0f , true ) ;
World - > GetTimerManager ( ) . SetTimer ( StuckTimerHandle , FTimerDelegate : : CreateLambda ( [ ] { } ) , 60.0f , true ) ;
}
2026-07-15 19:17:47 +02:00
class FDirectiveUtilTickTraceWorld : public IAutomationLatentCommand
{
public :
FDirectiveUtilTickTraceWorld ( FAutomationTestBase * InTest , UWorld * InWorld , const TArray < UDirectiveUtilDelegateListener * > & InListeners , int32 InFrames )
: Test ( InTest )
, World ( InWorld )
, Listeners ( InListeners )
, FramesRemaining ( InFrames )
{
}
virtual bool Update ( ) override
{
if ( UWorld * TickWorld = World . Get ( ) )
{
TickWorld - > Tick ( LEVELTICK_All , 0.05f ) ;
}
bool bAllComplete = true ;
for ( const UDirectiveUtilDelegateListener * Listener : Listeners )
{
if ( Listener & & ! Listener - > bCompleted )
{
bAllComplete = false ;
break ;
}
}
if ( bAllComplete | | - - FramesRemaining < = 0 )
{
for ( UDirectiveUtilDelegateListener * Listener : Listeners )
{
if ( ! Listener )
{
continue ;
}
Test - > TestTrue ( TEXT ( " Async trace broadcasts Completed " ) , Listener - > bCompleted ) ;
Test - > TestTrue ( TEXT ( " Async trace through a blocking cube reports a hit " ) , Listener - > HitCount > 0 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
if ( UWorld * TearDownWorld = World . Get ( ) )
{
GEngine - > DestroyWorldContext ( TearDownWorld ) ;
TearDownWorld - > DestroyWorld ( false ) ;
}
return true ;
}
return false ;
}
private :
FAutomationTestBase * Test ;
TWeakObjectPtr < UWorld > World ;
TArray < UDirectiveUtilDelegateListener * > Listeners ;
int32 FramesRemaining ;
} ;
2026-08-15 21:06:43 +02:00
IMPLEMENT_SIMPLE_AUTOMATION_TEST ( FDirectiveUtilAsyncTraceTest , " DirectiveUtilities.AsyncTaskTraceTests " , EAutomationTestFlags : : EditorContext | EAutomationTestFlags : : ClientContext | EAutomationTestFlags : : EngineFilter )
2026-07-15 19:17:47 +02:00
bool FDirectiveUtilAsyncTraceTest : : RunTest ( const FString & Parameters )
{
AddExpectedMessagePlain ( TEXT ( " Async Trace failed to activate. World is null. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
{
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_AsyncTrace * Task = UDirectiveUtilTask_AsyncTrace : : AsyncLineTraceByChannel ( nullptr , FVector : : ZeroVector , FVector ( 0 , 0 , 100 ) , ETraceTypeQuery : : TraceTypeQuery1 , false ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnTraceCompleted ) ;
Task - > Activate ( ) ;
TestTrue ( " Null world trace still broadcasts Completed " , Listener - > bCompleted ) ;
TestEqual ( " Null world trace reports no hits " , Listener - > HitCount , 0 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
UStaticMesh * CubeMesh = LoadObject < UStaticMesh > ( nullptr , TEXT ( " /Engine/BasicShapes/Cube.Cube " ) ) ;
if ( ! CubeMesh )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Engine cube mesh unavailable for async trace hit scenarios. " ) ) ;
return false ;
2026-07-15 19:17:47 +02:00
}
UWorld * World = DirectiveUtilAsyncTraceTestHelpers : : CreateTraceWorld ( ) ;
if ( ! World )
{
AddError ( TEXT ( " Failed to create a transient game world for the async trace test. " ) ) ;
return false ;
}
DirectiveUtilAsyncTraceTestHelpers : : SpawnBlockingCube ( World , CubeMesh ) ;
const FVector Start ( 0.0f , 0.0f , 500.0f ) ;
const FVector End ( 0.0f , 0.0f , - 500.0f ) ;
2026-08-15 21:06:43 +02:00
const ETraceTypeQuery VisibilityChannel = ETraceTypeQuery : : TraceTypeQuery1 ;
2026-07-15 19:17:47 +02:00
auto MakeListener = [ ] ( UDirectiveUtilTask_AsyncTrace * Task ) - > UDirectiveUtilDelegateListener *
{
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnTraceCompleted ) ;
Task - > Activate ( ) ;
return Listener ;
} ;
TArray < UDirectiveUtilDelegateListener * > Listeners ;
2026-08-15 21:06:43 +02:00
Listeners . Add ( MakeListener ( UDirectiveUtilTask_AsyncTrace : : AsyncLineTraceByChannel ( World , Start , End , VisibilityChannel , false ) ) ) ;
Listeners . Add ( MakeListener ( UDirectiveUtilTask_AsyncTrace : : AsyncSphereTraceByChannel ( World , Start , End , 25.0f , VisibilityChannel , false ) ) ) ;
Listeners . Add ( MakeListener ( UDirectiveUtilTask_AsyncTrace : : AsyncBoxTraceByChannel ( World , Start , End , FVector ( 25.0f ) , FRotator : : ZeroRotator , VisibilityChannel , false ) ) ) ;
Listeners . Add ( MakeListener ( UDirectiveUtilTask_AsyncTrace : : AsyncCapsuleTraceByChannel ( World , Start , End , 25.0f , 50.0f , VisibilityChannel , false ) ) ) ;
2026-07-15 19:17:47 +02:00
ADD_LATENT_AUTOMATION_COMMAND ( FDirectiveUtilTickTraceWorld ( this , World , Listeners , 120 ) ) ;
return true ;
}
2026-08-15 21:06:43 +02:00
IMPLEMENT_SIMPLE_AUTOMATION_TEST ( FDirectiveUtilMoveToLocationTest , " DirectiveUtilities.AsyncTaskMoveToLocationTests " , EAutomationTestFlags : : EditorContext | EAutomationTestFlags : : ClientContext | EAutomationTestFlags : : EngineFilter )
2026-07-15 19:17:47 +02:00
bool FDirectiveUtilMoveToLocationTest : : RunTest ( const FString & Parameters )
{
2026-08-15 21:06:43 +02:00
AddExpectedMessagePlain ( TEXT ( " Controller, pawn, or world is unavailable while moving to location. Aborting. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
2026-07-15 19:17:47 +02:00
AddExpectedMessagePlain ( TEXT ( " Controller or pawn has been destroyed while moving to location. Aborting. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
{
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToLocation * Task = UDirectiveUtilTask_MoveToLocation : : MoveToLocation ( nullptr , nullptr , FVector ( 100.0f , 0.0f , 0.0f ) ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
TestTrue ( " Null controller broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " Null controller reports failure " , Listener - > bLastSuccess ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
2026-08-15 21:06:43 +02:00
UWorld * World = UWorld : : CreateWorld ( EWorldType : : Game , false ) ;
2026-07-15 19:17:47 +02:00
if ( ! World )
{
AddError ( TEXT ( " Failed to create a transient world for the move-to-location test. " ) ) ;
return false ;
}
2026-08-15 21:06:43 +02:00
FWorldContext & WorldContext = GEngine - > CreateNewWorldContext ( EWorldType : : Game ) ;
2026-07-15 19:17:47 +02:00
WorldContext . SetCurrentWorld ( World ) ;
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToLocation * Task = UDirectiveUtilTask_MoveToLocation : : MoveToLocation ( World , Controller , FVector ( 100.0f , 0.0f , 0.0f ) ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
TestTrue ( " Controller without a pawn broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " Controller without a pawn reports failure " , Listener - > bLastSuccess ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToLocation * Task = UDirectiveUtilTask_MoveToLocation : : MoveToLocation ( World , Controller , FVector ( 100.0f , 0.0f , 0.0f ) ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > EndTask ( ) ;
TestTrue ( " EndTask broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " EndTask reports failure " , Listener - > bLastSuccess ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToLocation * Task = UDirectiveUtilTask_MoveToLocation : : MoveToLocation ( World , Controller , FVector ( 100.0f , 0.0f , 0.0f ) ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > EndTask ( ) ;
Task - > EndTask ( ) ;
TestEqual ( " Double EndTask broadcasts Completed exactly once " , Listener - > CompletedCount , 1 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
2026-08-15 21:06:43 +02:00
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
ADefaultPawn * Pawn = World - > SpawnActor < ADefaultPawn > ( ) ;
if ( Controller & & Pawn )
{
Controller - > SetPawn ( Pawn ) ;
UDirectiveUtilTestMoveToLocationTask * Task = NewObject < UDirectiveUtilTestMoveToLocationTask > ( ) ;
Task - > Configure ( Controller , FVector ( 1000.0f , 0.0f , 0.0f ) , true ) ;
Task - > RegisterTimersForTest ( World ) ;
TestTrue ( " Move to location registers both lifecycle timers " , Task - > HasRegisteredTimers ( ) ) ;
Task - > ClearController ( ) ;
Task - > Complete ( ) ;
TestFalse ( " Move to location clears timers without a controller " , Task - > HasRegisteredTimers ( ) ) ;
Task - > Configure ( Controller , FVector ( 1000.0f , 0.0f , 0.0f ) , true ) ;
Task - > Activate ( ) ;
TestFalse ( " A completed move to location should not restart " , Task - > HasRegisteredTimers ( ) ) ;
}
}
2026-07-15 19:17:47 +02:00
GEngine - > DestroyWorldContext ( World ) ;
World - > DestroyWorld ( false ) ;
{
UWorld * MoveWorld = DirectiveUtilAsyncTraceTestHelpers : : CreateTraceWorld ( ) ;
if ( ! MoveWorld )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Failed to create a transient game world for the no-navigation move scenario. " ) ) ;
return false ;
2026-07-15 19:17:47 +02:00
}
APlayerController * Controller = MoveWorld - > SpawnActor < APlayerController > ( ) ;
ADefaultPawn * Pawn = MoveWorld - > SpawnActor < ADefaultPawn > ( FVector : : ZeroVector , FRotator : : ZeroRotator ) ;
if ( ! Controller | | ! Pawn )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Failed to spawn a controller or pawn for the no-navigation move scenario. " ) ) ;
2026-07-15 19:17:47 +02:00
GEngine - > DestroyWorldContext ( MoveWorld ) ;
MoveWorld - > DestroyWorld ( false ) ;
2026-08-15 21:06:43 +02:00
return false ;
2026-07-15 19:17:47 +02:00
}
Controller - > SetPawn ( Pawn ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToLocation * Task = UDirectiveUtilTask_MoveToLocation : : MoveToLocation ( MoveWorld , Controller , FVector ( 10000.0f , 0.0f , 0.0f ) , 100.0f , false ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
2026-08-15 21:06:43 +02:00
TestTrue ( " A location move without navigation broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " A location move without navigation reports failure " , Listener - > bLastSuccess ) ;
TestEqual ( " A location move without navigation completes exactly once " , Listener - > CompletedCount , 1 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
GEngine - > DestroyWorldContext ( MoveWorld ) ;
MoveWorld - > DestroyWorld ( false ) ;
2026-07-15 19:17:47 +02:00
}
return true ;
}
2026-08-15 21:06:43 +02:00
IMPLEMENT_SIMPLE_AUTOMATION_TEST ( FDirectiveUtilMoveToActorTest , " DirectiveUtilities.AsyncTaskMoveToActorTests " , EAutomationTestFlags : : EditorContext | EAutomationTestFlags : : ClientContext | EAutomationTestFlags : : EngineFilter )
2026-07-15 19:17:47 +02:00
bool FDirectiveUtilMoveToActorTest : : RunTest ( const FString & Parameters )
{
2026-08-15 21:06:43 +02:00
AddExpectedMessagePlain ( TEXT ( " Controller, pawn, goal, or world is unavailable while moving to actor. Aborting. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
AddExpectedMessagePlain ( TEXT ( " Controller, pawn, or world is unavailable while moving to actor. Aborting. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
2026-07-15 19:17:47 +02:00
AddExpectedMessagePlain ( TEXT ( " Controller, pawn, or goal has been destroyed while moving to actor. Aborting. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
{
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToActor * Task = UDirectiveUtilTask_MoveToActor : : MoveToActor ( nullptr , nullptr , nullptr ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
TestTrue ( " Null controller broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " Null controller reports failure " , Listener - > bLastSuccess ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
2026-08-15 21:06:43 +02:00
UWorld * World = UWorld : : CreateWorld ( EWorldType : : Game , false ) ;
2026-07-15 19:17:47 +02:00
if ( ! World )
{
AddError ( TEXT ( " Failed to create a transient world for the move-to-actor test. " ) ) ;
return false ;
}
2026-08-15 21:06:43 +02:00
FWorldContext & WorldContext = GEngine - > CreateNewWorldContext ( EWorldType : : Game ) ;
2026-07-15 19:17:47 +02:00
WorldContext . SetCurrentWorld ( World ) ;
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
ADefaultPawn * Pawn = World - > SpawnActor < ADefaultPawn > ( FVector : : ZeroVector , FRotator : : ZeroRotator ) ;
if ( Controller & & Pawn )
{
Controller - > SetPawn ( Pawn ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToActor * Task = UDirectiveUtilTask_MoveToActor : : MoveToActor ( World , Controller , nullptr ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
TestTrue ( " Null goal broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " Null goal reports failure " , Listener - > bLastSuccess ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
else
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Failed to spawn a controller or pawn for the null-goal scenario. " ) ) ;
2026-07-15 19:17:47 +02:00
}
}
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToActor * Task = UDirectiveUtilTask_MoveToActor : : MoveToActor ( World , Controller , nullptr ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > EndTask ( ) ;
Task - > EndTask ( ) ;
TestEqual ( " Double EndTask broadcasts Completed exactly once " , Listener - > CompletedCount , 1 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
}
2026-08-15 21:06:43 +02:00
{
APlayerController * Controller = World - > SpawnActor < APlayerController > ( ) ;
ADefaultPawn * Pawn = World - > SpawnActor < ADefaultPawn > ( ) ;
AStaticMeshActor * Goal = World - > SpawnActor < AStaticMeshActor > ( ) ;
if ( Controller & & Pawn & & Goal )
{
Controller - > SetPawn ( Pawn ) ;
UDirectiveUtilTestMoveToActorTask * Task = NewObject < UDirectiveUtilTestMoveToActorTask > ( ) ;
Task - > Configure ( Controller , Goal , true ) ;
Task - > RegisterTimersForTest ( World ) ;
TestTrue ( " Move to actor registers both lifecycle timers " , Task - > HasRegisteredTimers ( ) ) ;
Task - > ClearController ( ) ;
Task - > Complete ( ) ;
TestFalse ( " Move to actor clears timers without a controller " , Task - > HasRegisteredTimers ( ) ) ;
Task - > Configure ( Controller , Goal , true ) ;
Task - > Activate ( ) ;
TestFalse ( " A completed move to actor should not restart " , Task - > HasRegisteredTimers ( ) ) ;
}
}
2026-07-15 19:17:47 +02:00
GEngine - > DestroyWorldContext ( World ) ;
World - > DestroyWorld ( false ) ;
{
UWorld * MoveWorld = DirectiveUtilAsyncTraceTestHelpers : : CreateTraceWorld ( ) ;
if ( ! MoveWorld )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Failed to create a transient game world for the no-navigation move scenario. " ) ) ;
return false ;
2026-07-15 19:17:47 +02:00
}
APlayerController * Controller = MoveWorld - > SpawnActor < APlayerController > ( ) ;
ADefaultPawn * Pawn = MoveWorld - > SpawnActor < ADefaultPawn > ( FVector : : ZeroVector , FRotator : : ZeroRotator ) ;
AStaticMeshActor * GoalActor = MoveWorld - > SpawnActor < AStaticMeshActor > ( FVector ( 10000.0f , 0.0f , 0.0f ) , FRotator : : ZeroRotator ) ;
if ( ! Controller | | ! Pawn | | ! GoalActor )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Failed to spawn a controller, pawn, or goal for the no-navigation move scenario. " ) ) ;
2026-07-15 19:17:47 +02:00
GEngine - > DestroyWorldContext ( MoveWorld ) ;
MoveWorld - > DestroyWorld ( false ) ;
2026-08-15 21:06:43 +02:00
return false ;
2026-07-15 19:17:47 +02:00
}
Controller - > SetPawn ( Pawn ) ;
UDirectiveUtilDelegateListener * Listener = NewObject < UDirectiveUtilDelegateListener > ( ) ;
Listener - > AddToRoot ( ) ;
UDirectiveUtilTask_MoveToActor * Task = UDirectiveUtilTask_MoveToActor : : MoveToActor ( MoveWorld , Controller , GoalActor , 100.0f , false ) ;
Listener - > Keepalive = Task ;
Task - > Completed . AddDynamic ( Listener , & UDirectiveUtilDelegateListener : : OnBoolCompleted ) ;
Task - > Activate ( ) ;
2026-08-15 21:06:43 +02:00
TestTrue ( " An actor move without navigation broadcasts Completed " , Listener - > bCompleted ) ;
TestFalse ( " An actor move without navigation reports failure " , Listener - > bLastSuccess ) ;
TestEqual ( " An actor move without navigation completes exactly once " , Listener - > CompletedCount , 1 ) ;
Listener - > Keepalive = nullptr ;
Listener - > RemoveFromRoot ( ) ;
GEngine - > DestroyWorldContext ( MoveWorld ) ;
MoveWorld - > DestroyWorld ( false ) ;
2026-07-15 19:17:47 +02:00
}
return true ;
}