From 3d4c56fcd929048796eeccaeec06d0edb4a907d5 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 25 Nov 2023 21:03:31 -0500 Subject: [PATCH] Write some unit tests for the input buffer --- .../Input/FFInputBufferComponent.cpp | 9 +- .../Input/FFInputBufferComponent.h | 2 + .../Tests/InputBufferTests.cpp | 139 ++++++++++++++++++ .../Utils/TCircleBuffer.h | 12 +- 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 Source/UnrealFightingFramework/Tests/InputBufferTests.cpp diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index 8c2072f..3ab4529 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -18,14 +18,14 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe { if(InputSequence.Sequence.IsEmpty()) { - UE_LOG(LogTemp, Error, + UE_LOG(LogTemp, Warning, TEXT("FFInputBufferComponent :: CheckInputSequence - tried to check input sequence but it was empty")); return false; } int CondIdx = InputSequence.Sequence.Num() - 1; int ElapsedFrames = 0; - for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 1; InpIdx--) + for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 0; InpIdx--) { int32 RequiredButtons = InputSequence.Sequence[CondIdx].RequiredButtons; EFFButtonState RequiredButtonState = InputSequence.Sequence[CondIdx].RequiredButtonState; @@ -100,3 +100,8 @@ void UFFInputBufferComponent::DisableMostRecentInput() InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons; InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons; } + +void UFFInputBufferComponent::FlushBuffer() +{ + InputBuffer.Flush(); +} diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 264d5dc..927ede1 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -101,6 +101,8 @@ public: void DisableMostRecentInput(); + void FlushBuffer(); + protected: /** The underlying buffer data structure for holding past input states */ TCircleBuffer InputBuffer; diff --git a/Source/UnrealFightingFramework/Tests/InputBufferTests.cpp b/Source/UnrealFightingFramework/Tests/InputBufferTests.cpp new file mode 100644 index 0000000..ff0c727 --- /dev/null +++ b/Source/UnrealFightingFramework/Tests/InputBufferTests.cpp @@ -0,0 +1,139 @@ +// Project Sword & Gun Copyright Kevin Poretti + +// FF includes +#include "Input/FFInputBufferComponent.h" + +// UE includes +#include "CoreMinimal.h" +#include "Misc/AutomationTest.h" + +#if WITH_DEV_AUTOMATION_TESTS + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FInputBufferTest, "FF.Input.InputBuffer", + EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) +bool FInputBufferTest::RunTest(const FString& Parameters) +{ + TStrongObjectPtr World = TStrongObjectPtr(UWorld::CreateWorld(EWorldType::Game, false)); + // Create a dummy actor + TStrongObjectPtr InputBufferOwner = TStrongObjectPtr(World->SpawnActor()); + TestTrue(TEXT("Create InputBufferOwner"), InputBufferOwner.IsValid()); + UFFInputBufferComponent* InputBuffer = + NewObject(InputBufferOwner.Get(), + UFFInputBufferComponent::StaticClass(), TEXT("InputBuffer")); + InputBuffer->RegisterComponent(); + + // InputBuffer must be able to be created as a subobject of an actor + { + TestNotNull(TEXT("Create new InputBuffer component"), InputBuffer); + } + + // InputBuffer should not attempt to evaluate an input sequence if there is only one input in + // the buffer and should return false + { + FFFInputState InputDown; + InputDown.Buttons = 0x00000001; + + InputBuffer->AddInput(InputDown); + + FFFInputCondition PressedCondition; + PressedCondition.RequiredButtons = 0x00000001; + PressedCondition.RequiredButtonState = EFFButtonState::BTNS_Pressed; + PressedCondition.TimeoutDuration = 0; + + FFFInputSequence PressedSequence; + PressedSequence.Sequence.Add(PressedCondition); + TestFalse(TEXT("Don't try to detect a sequence if only one input is present in the buffer"), + InputBuffer->CheckInputSequence(PressedSequence)); + } + + InputBuffer->FlushBuffer(); + + // InputBuffer should not attempt to evaluate an input sequence if the sequence has no input conditions + { + FFFInputState InputUp; + InputUp.Buttons = 0x00000000; + FFFInputState InputDown; + InputDown.Buttons = 0x00000001; + + InputBuffer->AddInput(InputUp); + InputBuffer->AddInput(InputDown); + + FFFInputSequence EmptySequence; + TestFalse(TEXT("Don't try to detect a sequence if the sequence has no input conditions"), + InputBuffer->CheckInputSequence(EmptySequence)); + } + + InputBuffer->FlushBuffer(); + + // InputBuffer must detect simple button pressed condition + { + FFFInputState InputUp; + InputUp.Buttons = 0x00000000; + FFFInputState InputDown; + InputDown.Buttons = 0x00000001; + + InputBuffer->AddInput(InputUp); + InputBuffer->AddInput(InputDown); + + FFFInputCondition PressedCondition; + PressedCondition.RequiredButtons = 0x00000001; + PressedCondition.RequiredButtonState = EFFButtonState::BTNS_Pressed; + PressedCondition.TimeoutDuration = 0; + + FFFInputSequence PressedSequence; + PressedSequence.Sequence.Add(PressedCondition); + TestTrue(TEXT("Detect a single button press in the buffer"), + InputBuffer->CheckInputSequence(PressedSequence)); + } + + InputBuffer->FlushBuffer(); + + // InputBuffer must detect simple button released condition + { + FFFInputState InputUp; + InputUp.Buttons = 0x0000000; + FFFInputState InputDown; + InputDown.Buttons = 0x00000001; + + InputBuffer->AddInput(InputDown); + InputBuffer->AddInput(InputUp); + + FFFInputCondition ReleasedCondition; + ReleasedCondition.RequiredButtons = 0x00000001; + ReleasedCondition.RequiredButtonState = EFFButtonState::BTNS_Released; + ReleasedCondition.TimeoutDuration = 0; + + FFFInputSequence ReleasedSequence; + ReleasedSequence.Sequence.Add(ReleasedCondition); + TestTrue(TEXT("Detect a single button release in the buffer"), + InputBuffer->CheckInputSequence(ReleasedSequence)); + } + + InputBuffer->FlushBuffer(); + + // InputBuffer must detect simple button down condition + { + FFFInputState InputUp; + InputUp.Buttons = 0x0000000; + FFFInputState InputDown; + InputDown.Buttons = 0x00000001; + + InputBuffer->AddInput(InputUp); + InputBuffer->AddInput(InputDown); + + FFFInputCondition DownCondition; + DownCondition.RequiredButtons = 0x00000001; + DownCondition.RequiredButtonState = EFFButtonState::BTNS_Down; + DownCondition.TimeoutDuration = 0; + + FFFInputSequence DownSequence; + DownSequence.Sequence.Add(DownCondition); + TestTrue(TEXT("Detect a single button press in the buffer"), + InputBuffer->CheckInputSequence(DownSequence)); + } + + World->DestroyWorld(false); + return true; +} + +#endif //WITH_DEV_AUTOMATION_TESTS diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h index 3d82b1a..8b08091 100644 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h @@ -108,6 +108,16 @@ public: return true; } + /** + * @brief Flushes the buffer of all contents + */ + void Flush() + { + WriteIdx = 0; + ReadIdx = 0; + _Num = 0; + } + /** * @brief Returns the element at an index supplied to the function. * This function will account for write index offset and wraparound of the supplied index. @@ -119,7 +129,7 @@ public: { return Buffer[(ReadIdx + Index) % L]; } - + FORCEINLINE bool IsEmpty() const { return _Num == 0;