From 2c5569b6d56a3e6dadaeeea222f34d505de53607 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 26 Jun 2023 21:34:58 -0400 Subject: [PATCH] Untested circle buffer implementation --- .../Input/FFInputBufferComponent.cpp | 12 +- .../Input/FFInputBufferComponent.h | 21 ++- .../Input/FFPlayerController.cpp | 2 +- .../Input/FFPlayerController.h | 19 +-- .../UnrealFightingFramework.Build.cs | 5 +- .../Utils/TCircleBuffer.cpp | 11 -- .../Utils/TCircleBuffer.h | 143 +++++++++++++++++- 7 files changed, 168 insertions(+), 45 deletions(-) delete mode 100644 Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index e4e7c97..f7d1aa4 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -7,16 +7,18 @@ UFFInputBufferComponent::UFFInputBufferComponent() PrimaryComponentTick.bCanEverTick = false; } -void UFFInputBufferComponent::Initialize(int32 BufferSize) -{ - Buffer.Reserve(BufferSize); -} - void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) { + InputBuffer.ForcePush(InputState); } bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) { + for(int InpIdx = 0; InpIdx < InputBuffer.Num(); InpIdx++) + { + // read input sequence + FFFInputState CurrInput = InputBuffer[InpIdx]; + } + return true; } diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index b425dbd..a2992f4 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -2,14 +2,27 @@ #pragma once +// FF includes +#include "Utils/TCircleBuffer.h" + // UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" -#include "Containers/RingBuffer.h" #include "FFInputBufferComponent.generated.h" -struct FFFInputState; +/** + * Struct representing the state of a player's inputs for one frame + */ +USTRUCT() +struct FFFInputState +{ + GENERATED_BODY() + + FVector2D MoveAxes; + FVector2D LookAxes; + int32 Buttons; +}; USTRUCT() struct FFFInputSequence @@ -26,13 +39,11 @@ class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorCompone public: UFFInputBufferComponent(); - void Initialize(int32 BufferSize = 120); - void AddInput(const FFFInputState& InputState); bool CheckInputSequence(const FFFInputSequence& InputSequence); protected: /** The underlying buffer data structure for holding past input states */ - TRingBuffer Buffer; + TCircleBuffer InputBuffer; }; diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index d55b53e..1b4b9a1 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -21,7 +21,7 @@ void AFFPlayerController::SendInputsToRemote() const void AFFPlayerController::FixedTick(float OneFrame) { - UnacknowledgedInputs.Add(CurrInput); + //UnacknowledgedInputs.Push(CurrInput); InputBuffer->AddInput(CurrInput); SendInputsToRemote(); diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index bf74650..99ba73a 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -4,29 +4,16 @@ // FF includes #include "FFInputBufferComponent.h" -#include "UnrealFightingFramework/IFFSystemInterface.h" +#include "IFFSystemInterface.h" +#include "Utils/TCircleBuffer.h" // UE includes #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "InputMappingContext.h" -#include "Containers/RingBuffer.h" #include "FFPlayerController.generated.h" -/** - * Struct representing the state of a player's inputs for one frame - */ -USTRUCT() -struct FFFInputState -{ - GENERATED_BODY() - - FVector2D MoveAxes; - FVector2D LookAxes; - int32 Buttons; -}; - /** * A class that collects player inputs, stores them in an input buffer, and sends a rolling window of * unacknowledged inputs to a remote client or server for processing. @@ -72,5 +59,5 @@ protected: * you want the remote machine to re-simulate, where X is the oldest input you want to * allow to be re-simulated. */ - TRingBuffer UnacknowledgedInputs; + TCircleBuffer UnacknowledgedInputs; }; diff --git a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs index a2e4e2d..b6a13a9 100644 --- a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs +++ b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs @@ -1,5 +1,5 @@ // Unreal Fighting Framework by Kevin Poretti - +using System.IO; using UnrealBuildTool; public class UnrealFightingFramework : ModuleRules @@ -10,14 +10,13 @@ public class UnrealFightingFramework : ModuleRules PublicIncludePaths.AddRange( new string[] { - "UnrealFightingFramework" + Path.Combine(PluginDirectory,"Source/UnrealFightingFramework"), } ); PrivateIncludePaths.AddRange( new string[] { - "UnrealFightingFramework" } ); diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp b/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp deleted file mode 100644 index c6e0643..0000000 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - -#include "Utils/TCircleBuffer.h" - -TCircleBuffer::TCircleBuffer() -{ -} - -TCircleBuffer::~TCircleBuffer() -{ -} diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h index a1debaa..1289eaf 100644 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h @@ -5,11 +5,146 @@ #include "CoreMinimal.h" /** - * + * CircleBuffer is a templated array data structure allowing for FIFO, O(1) insertion and removal, + * and wraparound when iterating/accessing elements of the buffer. + * + * The underlying container type is a statically allocated array. You can use the second + * template parameter to specify the static size of the buffer. Defaults to 64. */ +template class UNREALFIGHTINGFRAMEWORK_API TCircleBuffer { public: - TCircleBuffer(); - ~TCircleBuffer(); -}; + TCircleBuffer() + : WriteIdx(0) + , ReadIdx(0) + { + } + + /** + * @brief Adds a new element to the buffer only if there is room. + * @param Element to add to the buffer + * @return true if there was room in the buffer and the element was added, false otherwise + */ + bool Push(const T& Element) + { + if(_Num == L) + { + // buffer full so can't add element + return false; + } + Buffer[WriteIdx] = Element; + WriteIdx = (WriteIdx + 1) % L; + _Num++; + return true; + } + + /** + * @brief Adds a new element to the buffer. + * This function always adds an element. If the buffer is full the oldest element will be overwritten. + * @param Element to add to the buffer + */ + void ForcePush(const T& Element) + { + if(_Num == L) + { + // buffer is full so we need to overwrite the oldest element + // and make the read index point to the next oldest element + ReadIdx = (ReadIdx + 1) % L; + } + Buffer[WriteIdx] = Element; + WriteIdx = (WriteIdx + 1) % L; + _Num = FMath::Min(_Num + 1, L); + } + + /** + * @brief Returns the oldest element in the buffer and removes that element from the buffer. + * @param Result out parameter to be filled in with the popped element + * @return true if there was an element in the buffer to pop, false otherwise + */ + bool Pop(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[ReadIdx]; + ReadIdx = (ReadIdx + 1) % L; + _Num--; + return true; + } + + /** + * @brief Returns the newest element that was added to the buffer + * @param Result out parameter to be filled in with the most recently added element + * @return true if there was an element in the buffer to peak, false otherwise + */ + bool PeakLast(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[((SSIZE_T)WriteIdx - 1) <= 0 ? L - 1 : (WriteIdx - 1)]; + return true; + } + + /** + * @brief Returns the oldest element that was added to the buffer + * @param Result out parameter to be filled in with the oldest element + * @return true if there was an element in the buffer to peak, false otherwise + */ + bool PeakFirst(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[ReadIdx]; + return true; + } + + /** + * @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. + * i.e. Index 0 will be the oldest added element while index Num - 1 will be the newest added element + * @param Index of the element to return + * @return the element at the index supplied + */ + T& operator[](SIZE_T Index) + { + return Buffer[((SSIZE_T)WriteIdx - (SSIZE_T)Index) < 0 ? L - (Index - WriteIdx) : WriteIdx - Index]; + } + + FORCEINLINE bool IsEmpty() const + { + return _Num == 0; + } + + /** + * @brief Returns the max size of the underlying buffer + * @return Max size of the underlying buffer + */ + FORCEINLINE SIZE_T Max() const + { + return L; + } + + /** + * @brief Returns the current number of elements of the underlying buffer + * @return Max size of the underlying buffer + */ + FORCEINLINE SIZE_T Num() const + { + return _Num; + } + +private: + T Buffer[L]; + SIZE_T WriteIdx; + SIZE_T ReadIdx; + SIZE_T _Num; +}; \ No newline at end of file