Note: These are very advanced types!
Not fully finished, but it makes handling std::atomic much easier because you no longer need to use its complicated methods. Also, this replaces the deprecated TAtomic<> that Unreal used to have.
#pragma once
#include <atomic>
#define USE_LEGACY_SWAP 0
using std::atomic;
using std::memory_order;
using std::memory_order_acquire;
using std::memory_order_release;
using std::memory_order_relaxed;
// Simplified std::atomic wrapper.
template<typename T>
struct FAtomic
{
// CONSTRUCTORS AND DESTRUCTOR -----------------------------------------------------------------------------------------
#pragma region ConstructorsAndDestructor
FAtomic() : Value{} {}
~FAtomic() = default;
explicit FAtomic(T InitValue) : Value(InitValue) {}
#pragma endregion
// OPERATORS ===========================================================================================================
#pragma region Operators
// ==
FAtomic<T>& operator=(const T& NewValue)
{
Set(NewValue);
return *this;
}
// ++T
FAtomic<T>& operator++()
{
Increment();
return *this;
}
// T++
FAtomic<T> operator++(int)
{
FAtomic<T> Previous = *this;
Increment();
return Previous;
}
// --T
FAtomic<T>& operator--()
{
Decrement();
return *this;
}
// T--
FAtomic<T> operator--(int)
{
FAtomic<T> Previous = *this;
Decrement();
return Previous;
}
// ==
bool operator==(const FAtomic<T>& Other) const
{
return Value.load(DefaultMemoryOrderRead) == Other.Value.load(DefaultMemoryOrderRead);
}
bool operator==(const T& OtherValue) const
{
return Value.load(DefaultMemoryOrderRead) == OtherValue;
}
// !=
bool operator!=(const FAtomic<T>& Other) const
{
return Value.load(DefaultMemoryOrderRead) != Other.Value.load(DefaultMemoryOrderRead);
}
bool operator!=(const T& OtherValue) const
{
return Value.load(DefaultMemoryOrderRead) != OtherValue;
}
// >=
bool operator>=(const FAtomic<T>& Other) const
{
return Value.load(DefaultMemoryOrderRead) >= Other.Value.load(DefaultMemoryOrderRead);
}
bool operator>=(const T& OtherValue) const
{
return Value.load(DefaultMemoryOrderRead) >= OtherValue;
}
// <=
bool operator<=(const FAtomic<T>& Other) const
{
return Value.load(DefaultMemoryOrderRead) <= Other.Value.load(DefaultMemoryOrderRead);
}
bool operator<=(const T& OtherValue) const
{
return Value.load(DefaultMemoryOrderRead) <= OtherValue;
}
#pragma endregion
// METHODS =============================================================================================================
#pragma region Methods
public:
// Safely get the atomic value.
[[nodiscard]] T Get(memory_order Order = DefaultMemoryOrderRead) const
{
return Value.load(Order);
}
// Safely set the atomic value.
void Set(
T ValueIn,
memory_order Order = DefaultMemoryOrderWrite)
{
Value.store(ValueIn, Order);
}
// Safely swap (0 -> 1 / 1 -> 0 | true -> false / false -> true) the atomic integral value.
template<typename U = T, std::enable_if_t<std::is_integral_v<U>, int> = 0>
void SwapIndex()
{
T CurrentValue = Value.load(memory_order_relaxed);
while (!Value.compare_exchange_weak(
CurrentValue,
1 - CurrentValue,
memory_order_release,
memory_order_relaxed));
}
private:
void Increment(memory_order Order = DefaultMemoryOrderWrite)
{
if constexpr (std::is_same_v<T, bool>)
{
bool CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, !CurrentValue));
}
else if constexpr (std::is_integral_v<T>)
{
Value.fetch_add(1);
}
else if constexpr (std::is_floating_point_v<T>)
{
T CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, CurrentValue + 1.0));
}
}
void Decrement(memory_order Order = DefaultMemoryOrderWrite)
{
if constexpr (std::is_same_v<T, bool>)
{
bool CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, !CurrentValue));
}
else if constexpr (std::is_integral_v<T>)
{
Value.fetch_sub(1);
}
else if constexpr (std::is_floating_point_v<T>)
{
T CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, CurrentValue - 1.0));
}
}
void Add(T Addition, memory_order Order = DefaultMemoryOrderWrite)
{
if constexpr (std::is_integral_v<T>)
{
Value.fetch_add(Addition);
}
else if constexpr (std::is_floating_point_v<T>)
{
T CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, CurrentValue + Addition));
}
}
void Subtract(T Subtraction, memory_order Order = DefaultMemoryOrderWrite)
{
if constexpr (std::is_integral_v<T>)
{
Value.fetch_sub(Subtraction);
}
else if constexpr (std::is_floating_point_v<T>)
{
T CurrentValue = Value.load();
while (!Value.compare_exchange_weak(CurrentValue, CurrentValue - Subtraction));
}
}
#pragma endregion
// VARIABLES ===========================================================================================================
#pragma region Variables
private:
// Template type stored atomic value.
atomic<T> Value;
// Compiler const memory orders.
static constexpr memory_order DefaultMemoryOrderRead = memory_order_acquire;
static constexpr memory_order DefaultMemoryOrderWrite = memory_order_release;
#pragma endregion
};
Why this? Cause there is no real easily usable way to save string on the strack and then work with it, for example in array. Things like "TArray<TCHAR[32]>" sadly won't work. Therefore this struct is a great solution, especially for data-oriented-programming.
#pragma once
#include "Misc/CString.h"
#include "Containers/StringFwd.h"
#include "Containers/UnrealString.h"
template <int32 BufferSize = 32>
struct FSimpleString
{
/** Default constructor (creates an empty string). */
FSimpleString()
{
Name[0] = TEXT('\0');
}
/** Constructor from a string literal. */
explicit FSimpleString(const TCHAR* InString)
{
FCString::Strncpy(Name, InString, BufferSize);
Name[BufferSize - 1] = TEXT('\0');
}
/** Constructor from a TStringBuilder. */
template <int32 OtherBufferSize>
explicit FSimpleString(TStringBuilder<OtherBufferSize>& Builder)
{
FCString::Strncpy(Name, *Builder, OtherBufferSize);
Name[BufferSize - 1] = TEXT('\0');
}
/** Constructor from another FSimpleString. */
template <int32 OtherBufferSize>
explicit FSimpleString(const FSimpleString<OtherBufferSize> &SimpleString)
{
FCString::Strncpy(Name, static_cast<const TCHAR*>(SimpleString), OtherBufferSize);
Name[BufferSize - 1] = TEXT('\0');
}
/** Constructor from an FString. */
explicit FSimpleString(const FString& String)
{
FCString::Strncpy(Name, *String, BufferSize);
Name[BufferSize - 1] = TEXT('\0');
}
// -----------------------------------------------------------------------------------------------------------------
/** Assignment operator from a string literal. */
FSimpleString<BufferSize>& operator=(const TCHAR* InString)
{
FCString::Strncpy(Name, InString, BufferSize);
Name[BufferSize - 1] = TEXT('\0');
return *this;
}
/** Assignment operator from a TStringBuilder. */
template <int32 OtherBufferSize>
FSimpleString<BufferSize>& operator=(TStringBuilder<OtherBufferSize>& Builder)
{
FCString::Strncpy(Name, *Builder, BufferSize);
Name[BufferSize - 1] = TEXT('\0');
return *this;
}
/** Assignment operator from an FString. */
FSimpleString<BufferSize>& operator=(const FString& String)
{
FCString::Strncpy(Name, *String, BufferSize);
Name[BufferSize - 1] = TEXT('\0');
return *this;
}
/** Assignment operator from another FSimpleString. */
template <int32 OtherBufferSize>
FSimpleString<BufferSize>& operator=(const FSimpleString<OtherBufferSize>& Other)
{
FCString::Strncpy(Name, static_cast<const TCHAR*>(Other), BufferSize);
Name[BufferSize - 1] = TEXT('\0');
return *this;
}
// -----------------------------------------------------------------------------------------------------------------
/** Concatenation assignment operator from an FSimpleString. */
template <int32 OtherBufferSize>
FSimpleString<BufferSize>& operator+=(const FSimpleString<OtherBufferSize>& Rhs)
{
Append(Rhs);
return *this;
}
/** Concatenation assignment operator from a string literal. */
FSimpleString<BufferSize>& operator+=(const TCHAR* Rhs)
{
Append(Rhs);
return *this;
}
/** Concatenation assignment operator from an FString. */
FSimpleString<BufferSize>& operator+=(const FString& Rhs)
{
Append(Rhs);
return *this;
}
/** Concatenation assignment operator from a TStringBuilder. */
template <int32 OtherBufferSize>
FSimpleString<BufferSize>& operator+=(const TStringBuilder<OtherBufferSize>& Rhs)
{
Append(Rhs);
return *this;
}
/** Concatenation assignment operator for standard types. */
FSimpleString<BufferSize>& operator+=(const int32 Rhs) { Append(Rhs); return *this; }
FSimpleString<BufferSize>& operator+=(const uint64 Rhs) { Append(Rhs); return *this; }
FSimpleString<BufferSize>& operator+=(const float Rhs) { Append(Rhs); return *this; }
FSimpleString<BufferSize>& operator+=(const double Rhs) { Append(Rhs); return *this; }
FSimpleString<BufferSize>& operator+=(const bool Rhs) { Append(Rhs); return *this; }
// -----------------------------------------------------------------------------------------------------------------
/** Allows passing this object directly to functions expecting a TCHAR*. */
// ReSharper disable once CppNonExplicitConversionOperator
operator const TCHAR*() const
{
return Name;
}
/** Comparison operator. */
bool operator==(const FSimpleString<BufferSize>& Other) const
{
return FCString::Strcmp(Name, Other.Name) == 0;
}
// -----------------------------------------------------------------------------------------------------------------
// String types.
/** Append string to the underlying "TCHAR Name[BufferSize]". */
void Append(const FString &String)
{
Append(*String);
}
/** Append string to the underlying "TCHAR Name[BufferSize]". */
template <int32 OtherBufferSize>
void Append(const FSimpleString<OtherBufferSize> &String)
{
Append(static_cast<const TCHAR*>(String));
}
/** Append string to the underlying "TCHAR Name[BufferSize]". */
template <int32 OtherBufferSize>
void Append(const TStringBuilder<OtherBufferSize> &String)
{
Append(*String);
}
/** Append string to the underlying "TCHAR Name[BufferSize]". */
void Append(const TCHAR* String)
{
if (!String)
return;
const int32 CurrentLength = FCString::Strlen(Name);
const int32 MaxAppendCount = BufferSize - CurrentLength - 1;
if (MaxAppendCount <= 0)
return;
FCString::StrncatTruncateDest(Name, BufferSize, String);
}
// Default Types.
/** Append integer to the underlying "TCHAR Name[BufferSize]". */
template<typename IntType>
void Append(const IntType Value)
{
if constexpr (std::is_integral_v<IntType> && !std::is_same_v<IntType, bool>)
{
if constexpr (std::is_signed_v<IntType>)
{
TCHAR Buffer[21];
FCString::Sprintf(Buffer, TEXT("%lld"), static_cast<long long>(Value));
Append(Buffer);
}
else
{
TCHAR Buffer[21];
FCString::Sprintf(Buffer, TEXT("%llu"), static_cast<unsigned long long>(Value));
Append(Buffer);
}
}
}
/** Append float to the underlying "TCHAR Name[BufferSize]". */
void Append(const float Value)
{
TCHAR Buffer[32];
FCString::Sprintf(Buffer, TEXT("%.7f"), Value);
Append(Buffer);
}
/** Append double to the underlying "TCHAR Name[BufferSize]". */
void Append(const double Value)
{
TCHAR Buffer[64];
FCString::Sprintf(Buffer, TEXT("%.17f"), Value);
Append(Buffer);
}
/** Append bool to the underlying "TCHAR Name[BufferSize]". */
void Append(const bool Value)
{
Append(Value ? TEXT("true") : TEXT("false"));
}
// -----------------------------------------------------------------------------------------------------------------
/** Checks if the string is empty. */
[[nodiscard]] bool IsEmpty() const
{
return Name[0] == TEXT('\0');
}
/** Get max BufferSize from a FSimpleString object. */
// ReSharper disable once CppMemberFunctionMayBeStatic
[[nodiscard]] constexpr int32 GetMaxBufferSize()
{
return BufferSize;
}
/** Get default BufferSize of FSimpleString<BufferSize>. */
[[nodiscard]] static constexpr int32 GetDefaultBufferSize()
{
return BufferSize;
}
/** Conversion to FString. */
[[nodiscard]] FString ToString() const
{
return FString(Name);
}
/** Conversion to TStringBuilder. */
void ToStringBuilder(TStringBuilder<BufferSize>& StringBuilder) const
{
StringBuilder.Clear();
StringBuilder << Name;
TArray<int32> Yes;
}
// -----------------------------------------------------------------------------------------------------------------
TCHAR Name[BufferSize];
};
// External Operators --------------------------------------------------------------------------------------------------
/** Enable usage in a hash-table. */
template <int32 BufferSize>
uint32 GetTypeHash(const FSimpleString<BufferSize>& SimpleString)
{
return FCrc::StrCrc32(static_cast<const TCHAR*>(SimpleString));
}
/** Concatenation Operator overload for FSimpleString "+" FSimpleString. */
template <int32 LhsBufferSize, int32 RhsBufferSize>
FSimpleString<LhsBufferSize + RhsBufferSize - 1> operator+(
const FSimpleString<LhsBufferSize>& Lhs,
const FSimpleString<RhsBufferSize>& Rhs)
{
FSimpleString<LhsBufferSize + RhsBufferSize - 1> Result(static_cast<const TCHAR*>(Lhs));
Result.Append(static_cast<const TCHAR*>(Rhs));
return Result;
}