// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Containers/Array.h" #include #include "IndexedHandle.generated.h" #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) #define UE_DO_INDEXED_HANDLE_MANAGER_ID 1 #else #define UE_DO_INDEXED_HANDLE_MANAGER_ID 0 #endif /** * Index based handle that doesn't use a serial number. * For fast access in to index based data structures when we don't expect handles to be able to be stale */ USTRUCT() struct FSimpleIndexedHandleBase { GENERATED_BODY() FSimpleIndexedHandleBase() = default; /** @param InIndex - passing INDEX_NONE will make this handle Invalid */ FSimpleIndexedHandleBase(int32 InIndex) : Index(InIndex) { }; bool operator==(const FSimpleIndexedHandleBase& Other) const { return Index == Other.Index; } bool operator!=(const FSimpleIndexedHandleBase& Other) const { return !operator==(Other); } /** @return INDEX_NONE if invalid or >=0 for a potentially valid handle */ int32 GetIndex() const { return Index; } /** @return true if this handle is valid, this doesn't check that it's in the correct range for the underlying resource it's providing a handle for */ bool IsValid() const { return Index >= 0; } /** * boolean operator useful when embedding declaration in if statement * @return same as IsValid() * @see IsValid */ operator bool() const { return IsValid(); } /** Makes the handle Invalid */ void Invalidate() { Index = INDEX_NONE; } /** @param InIndex - passing INDEX_NONE will make this handle Invalid */ void SetIndex(int32 InIndex) { Index = InIndex; } friend uint32 GetTypeHash(const FSimpleIndexedHandleBase& Handle) { return static_cast(Handle.GetIndex()); } protected: int32 Index = INDEX_NONE; }; /** Index based handle that has a serial number to verify stale handles. For fast safe access in to index based data structures */ USTRUCT() struct FIndexedHandleBase : public FSimpleIndexedHandleBase { GENERATED_BODY() FIndexedHandleBase() = default; /** @note passing INDEX_NONE as index will make this handle Invalid */ FIndexedHandleBase(int32 InIndex, uint32 InSerialNumber) : FSimpleIndexedHandleBase(InIndex), SerialNumber(InSerialNumber) { }; bool operator==(const FIndexedHandleBase& Other) const { return FSimpleIndexedHandleBase::operator==(Other) && SerialNumber == Other.SerialNumber; } bool operator!=(const FIndexedHandleBase& Other) const { return !operator==(Other); } uint32 GetSerialNumber() const { return SerialNumber; } void SetSerialNumber(uint32 InSerialNumber) { SerialNumber = InSerialNumber; } friend uint32 GetTypeHash(const FIndexedHandleBase& Handle) { return HashCombine(static_cast(Handle.GetIndex()), Handle.GetSerialNumber()); } protected: uint32 SerialNumber = 0; #if UE_DO_INDEXED_HANDLE_MANAGER_ID public: uint32 ManagerID = 0; #endif }; /** Compact Index based handle that has a serial number to verify stale handles. For fast safe access in to index based data structures. */ USTRUCT() struct FCompactIndexedHandleBase { GENERATED_BODY() FCompactIndexedHandleBase() = default; /** @note passing INDEX_NONE as index will make this handle Invalid */ FCompactIndexedHandleBase(const int32 InIndex, const uint32 InSerialNumber) { check(InIndex <= (int32)MAX_int16); Index = (int16)InIndex; check(InSerialNumber <= (uint32)MAX_uint16); SerialNumber = (uint16)InSerialNumber; }; bool operator==(const FCompactIndexedHandleBase& Other) const { return Index == Other.Index && SerialNumber == Other.SerialNumber; } bool operator!=(const FCompactIndexedHandleBase& Other) const { return !operator==(Other); } /** @return INDEX_NONE if invalid or >=0 for a potentially valid handle */ int16 GetIndex() const { return Index; } /** @return true if this handle is valid, this doesn't check that it's in the correct range for the underlying resource it's providing a handle for */ bool IsValid() const { return Index >= 0; } /** * boolean operator useful when embedding declaration in if statement * @return same as IsValid() * @see IsValid */ operator bool() const { return IsValid(); } /** Makes the handle Invalid */ void Invalidate() { Index = INDEX_NONE; } /** @param InIndex - passing INDEX_NONE will make this handle Invalid */ void SetIndex(int16 InIndex) { Index = InIndex; } uint16 GetSerialNumber() const { return SerialNumber; } void SetSerialNumber(int16 InSerialNumber) { SerialNumber = InSerialNumber; } friend uint32 GetTypeHash(const FCompactIndexedHandleBase& Handle) { return (static_cast(Handle.GetIndex()) << 16) + static_cast(Handle.GetSerialNumber()); } protected: int16 Index = INDEX_NONE; uint16 SerialNumber = 0; #if UE_DO_INDEXED_HANDLE_MANAGER_ID public: uint32 ManagerID = 0; #endif }; /** Handle Manager meant for FIndexedHandleBase and FCompactIndexedHandleBase derived classes, handles are given out from a freelist and are zero based and consecutive in nature, * so ideal for being used as indices in to arrays. * Using bOptimizeHandleReuse performance impact on releasing a handle. Because new handles will always use the smallest index, this will help greatly the compaction code(ShrinkHandles) and reduce the total number of handles. * This is very useful when you need to iterate through every valid handles often. */ template struct FIndexedHandleManagerBase { protected: typedef TArray FHandleArray; public: FIndexedHandleManagerBase() { #if UE_DO_INDEXED_HANDLE_MANAGER_ID ManagerID = ManagerIDCounter.fetch_add(1); #endif } TIndexedHandle GetNextHandle() { TIndexedHandle* Handle = nullptr; //if no free handle indices then create one if (FreeHandleIndices.Num() == 0) { checkf(Handles.Num() <= static_cast(TNumericLimits::Max()), TEXT("Handles are overflowing the numeric limits of the handle TIndexType!")); Handle = &Handles.Emplace_GetRef(Handles.Num(), SerialNumberCounter.fetch_add(1)); } else //otherwise use an existing handle index { const TIndexType Idx = FreeHandleIndices.Pop(); Handle = &Handles[Idx]; checkf(!Handle->IsValid(), TEXT("Free handle must be set invalid before reuse")); Handle->SetIndex(Idx); Handle->SetSerialNumber(SerialNumberCounter.fetch_add(1)); } #if UE_DO_INDEXED_HANDLE_MANAGER_ID Handle->ManagerID = ManagerID; #endif return *Handle; } bool RemoveHandle(TIndexedHandle IndexedHandle) { const bool bIsValid = IsValidHandle(IndexedHandle); if (ensureMsgf(bIsValid, TEXT("Trying to Remove Invalid Handle with Index %d, Serial Number %d"), IndexedHandle.GetIndex(), IndexedHandle.GetSerialNumber())) { if constexpr (bOptimizeHandleReuse) { const TIndexType Idx = IndexedHandle.GetIndex(); const int32 InsertPosition = Algo::LowerBound(FreeHandleIndices, Idx, [](const TIndexType& Lhs, const TIndexType& Rhs) {return Lhs > Rhs;}); FreeHandleIndices.Insert(Idx, InsertPosition); } else { FreeHandleIndices.Add(IndexedHandle.GetIndex()); } Handles[IndexedHandle.GetIndex()].Invalidate(); //increase the serial numbers when a handle is removed Handles[IndexedHandle.GetIndex()].SetSerialNumber(SerialNumberCounter.fetch_add(1)); } return bIsValid; } bool IsValidHandle(TIndexedHandle IndexedHandle) const { #if UE_DO_INDEXED_HANDLE_MANAGER_ID if (!ensureMsgf(ManagerID == IndexedHandle.ManagerID, TEXT("ManagerID %d does not match IndexedHandle.ManagerID %d, handles must only be used by the same manager they were created by!"), ManagerID, IndexedHandle.ManagerID)) { return false; } #endif // UE_DO_INDEXED_HANDLE_MANAGER_ID return ((IndexedHandle.GetIndex() >= 0) && (IndexedHandle.GetIndex() < Handles.Num()) && (IndexedHandle.GetSerialNumber() == Handles[IndexedHandle.GetIndex()].GetSerialNumber()) && (IndexedHandle.GetIndex() == Handles[IndexedHandle.GetIndex()].GetIndex())); } const TArray& GetHandles() const { return Handles; } /** returns the number of used handles, will be greater than or equal to zero */ int32 CalcNumUsedHandles() const { return Handles.Num() - FreeHandleIndices.Num(); }; // Defaulted copy constructor, but it acts differently from the copy assignment operator - notably, doesn't allocate a new ManagerID. FIndexedHandleManagerBase(const FIndexedHandleManagerBase& Other) = default; FIndexedHandleManagerBase& operator=(const FIndexedHandleManagerBase& Other) { Handles = Other.Handles; FreeHandleIndices = Other.FreeHandleIndices; //Leave SerialNumberCounter well alone #if UE_DO_INDEXED_HANDLE_MANAGER_ID //assign new ManagerID to the new Manager ManagerID = ManagerIDCounter.fetch_add(1); #endif // UE_DO_INDEXED_HANDLE_MANAGER_ID return *this; } /** Attempts to shrink the Handles array if there are contiguous free slots at the end of the Array * @return the new Num items in the handles array */ int32 ShrinkHandles() { while (Handles.Num() > 0 && FreeHandleIndices.Num() > 0 && FreeHandleIndices.Remove(Handles.Num() - 1)) { Handles.Pop(EAllowShrinking::No); } return Handles.Num(); } void Reset() { Handles.Reset(); FreeHandleIndices.Reset(); //Don't reset the SerialNumberCounter as we need that to increase from where it left off to keep handle consistency } protected: /** Handles stored as a free list sparse array, Handle entries that are free will have a GetIndex() == INDEX_NONE */ FHandleArray Handles; TArray FreeHandleIndices; static std::atomic SerialNumberCounter; #if UE_DO_INDEXED_HANDLE_MANAGER_ID /** ID used in non shipping / test builds to ensure the same manager that is used to create handles is also used to remove them */ static std::atomic ManagerIDCounter; uint32 ManagerID = 0; #endif // UE_DO_INDEXED_HANDLE_MANAGER_ID }; template std::atomic FIndexedHandleManagerBase::SerialNumberCounter = { 0 }; #if UE_DO_INDEXED_HANDLE_MANAGER_ID template std::atomic FIndexedHandleManagerBase::ManagerIDCounter = { 0 }; #endif // UE_DO_INDEXED_HANDLE_MANAGER_ID template struct FIndexedHandleManager : public FIndexedHandleManagerBase { }; template struct FCompactIndexedHandleManager : public FIndexedHandleManagerBase { };