Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Substrate/Include/Substrate/AllocatorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Substrate {
virtual size_t GetUsedMemory() const = 0;
virtual size_t GetCurrentAllocationCount() const = 0;
virtual size_t GetTotalAllocationCount() const = 0;
virtual size_t GetMaxedGenerationCount() const = 0;
virtual size_t GetResetCount() const = 0;
#endif
};
Expand Down
21 changes: 7 additions & 14 deletions Substrate/Include/Substrate/BaseHandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ namespace Substrate {
class BaseHandle
{
public:
static_assert(GenerationMask != static_cast<THandleType>(~static_cast<THandleType>(0)), "GenerationMask cannot be all 1s");

constexpr BaseHandle() : m_Handle(0) {};
constexpr BaseHandle(THandleType index) : m_Handle(index)
{
Expand All @@ -76,23 +78,14 @@ namespace Substrate {
}


/// <returns>Only true if the generation is not equal to the generation mask. Except when there are no generation bits, which is always a valid generation. Returns false if index == indexMask</returns>
/// <returns>Only true if the generation is not equal to the generation mask. This also covers GenerationMask = 0 -> the only invalid handle IS INVALID_HANDLE -> Index maxed out.
/// Or, generationBits maxed and index maxed, which is invalid because generation is maxed out.</returns>
constexpr bool IsValid()
{
//If the handle's index bits are all set to 1, the handle is invalid.
constexpr THandleType indexMask = GetIndexMask();
if ((m_Handle & indexMask) == indexMask)
return false;

//If the generation mask is 0, the handle is always valid.
if(GenerationMask == 0)
if constexpr(GenerationMask == 0)
return true;

//We return false if the generation mask is all bits set, because that means there is no valid index possible.
if (GenerationMask == static_cast<THandleType>((~static_cast<THandleType>(0))))
return false;

return (m_Handle & GenerationMask) != GenerationMask;
else
return (m_Handle & GenerationMask) != GenerationMask;
}

/// <returns>This is only true if the generations are equal AND the index! </returns>
Expand Down
1 change: 1 addition & 0 deletions Substrate/Include/Substrate/LinearAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace Substrate {
inline size_t GetTotalMemory() const override { return m_TotalSize; }
inline size_t GetTotalAllocationCount() const override { return m_TotalAllocationCount; }
inline size_t GetCurrentAllocationCount() const override { return m_CurrentAllocationCount; }
inline size_t GetMaxedGenerationCount() const override { return 0; }
inline size_t GetResetCount() const override { return m_ResetCount; }
#endif
private:
Expand Down
41 changes: 30 additions & 11 deletions Substrate/Include/Substrate/PoolAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace Substrate {
bool IsHandleValid(TResourceHandle handle);
void Free(TResourceHandle handle);

const std::vector<uint32_t>& GetFreeHandleIndices() const { return m_FreeHandles; }
const uint32_t GetFreeHandleCount() const { return m_FreeCount; }
using InternalHandle = DefineHandle<GENERATION_BIT_COUNT, INDEX_BIT_COUNT, TResourceHandle>;
const std::vector<InternalHandle>& GetHandles() const { return m_Handles; }

Expand All @@ -48,6 +48,7 @@ namespace Substrate {
inline size_t GetUsedMemory() const override { return m_CurrentAllocationCount * sizeof(TBlockType); }
inline size_t GetCurrentAllocationCount() const override { return m_CurrentAllocationCount; }
inline size_t GetTotalAllocationCount() const override { return m_TotalAllocationCount; }
inline size_t GetMaxedGenerationCount() const override { return m_MaxedGenerationCount; }
inline size_t GetResetCount() const override { return 0; }
#endif
private:
Expand All @@ -59,6 +60,10 @@ namespace Substrate {
size_t m_TotalAllocationCount = 0;
std::vector<InternalHandle> m_Handles;
std::vector<uint32_t> m_FreeHandles;
uint32_t m_FreeHead = 0;
uint32_t m_FreeTail = 0;
uint32_t m_FreeCount = 0;
uint32_t m_MaxedGenerationCount = 0;
};


Expand All @@ -75,11 +80,15 @@ namespace Substrate {
m_Handles.reserve(MAX_BLOCK_COUNT);
m_FreeHandles.reserve(MAX_BLOCK_COUNT);

m_FreeCount = MAX_BLOCK_COUNT;
m_FreeHead = 0;
m_FreeTail = 0;

// Initialize handles in reverse order for better cache locality
for (uint32_t i = 0; i < MAX_BLOCK_COUNT; i++)
{
m_Handles.push_back(InternalHandle(i));
m_FreeHandles.push_back(MAX_BLOCK_COUNT-1-i);
m_FreeHandles.push_back(i);
}
}

Expand Down Expand Up @@ -108,24 +117,29 @@ namespace Substrate {
m_TotalSize = 0;
m_Handles.clear();
m_FreeHandles.clear();
m_FreeHead = 0;
m_FreeTail = 0;
//Currently blocks further usage of the Allocator
m_FreeCount = 0;
}

template<typename TBlockType, typename TResourceHandle, size_t TSize>
requires HandleTypeCheck<TResourceHandle> && MustBePowerOFTwo<TSize>
TResourceHandle PoolAllocator<TBlockType, TResourceHandle, TSize>::Allocate()
{
// Check if there are free handles
if(m_FreeHandles.size() == 0)
if(m_FreeCount == 0)
throw AllocatorOutOfMemoryException("Pool allocator out of memory");

// Get the next free handle
uint32_t idx = m_FreeHandles.back();
m_FreeHandles.pop_back();
uint32_t idx = m_FreeHandles[m_FreeHead];
m_FreeHead = (m_FreeHead + 1) % MAX_BLOCK_COUNT;
//Calls constructor -> sets default values
new(m_MemoryBlock + idx) TBlockType{};

m_CurrentAllocationCount++;
m_TotalAllocationCount++;
m_FreeCount--;
return m_Handles[idx].GetRaw();
}

Expand All @@ -135,9 +149,9 @@ namespace Substrate {
{
InternalHandle handle = InternalHandle::FromRawType(resourceHandle);
InternalHandle& internal = m_Handles[handle.Index()];
if (!internal.Equals(handle))
return false;
return true;
if (internal.IsValid() && internal.Equals(handle))
return true;
return false;
}

template<typename TBlockType, typename TResourceHandle, size_t TSize>
Expand All @@ -161,16 +175,21 @@ namespace Substrate {
// Validate handle
InternalHandle handle = InternalHandle::FromRawType(resourceHandle);
InternalHandle& internal = m_Handles[handle.Index()];
if (!internal.Equals(handle))
if (!internal.Equals(handle) || !internal.IsValid())
return; // Invalid handle or handle was already freed

internal = internal.IncrementGeneration();

// Check if generation is maxed out
if(!internal.IsValid())
if (!internal.IsValid())
{
m_MaxedGenerationCount++;
return; // Cannot free handle anymore
}

m_FreeHandles.push_back(internal.Index());
m_FreeHandles[m_FreeTail] = internal.Index();
m_FreeCount++;
m_FreeTail = (m_FreeTail + 1) % MAX_BLOCK_COUNT;

// Decrease allocation count only if handle was put back into the free list
m_CurrentAllocationCount--;
Expand Down
1 change: 1 addition & 0 deletions Substrate/Include/Substrate/StackAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace Substrate {
inline size_t GetTotalMemory() const override { return m_TotalSize; }
inline size_t GetTotalAllocationCount() const override { return m_TotalAllocationCount; }
inline size_t GetCurrentAllocationCount() const override { return m_CurrentAllocationCount; }
inline size_t GetMaxedGenerationCount() const override { return 0; }
inline size_t GetResetCount() const override { return m_ResetCount; }
#endif
private:
Expand Down
22 changes: 8 additions & 14 deletions SubstrateTests/Tests/TestAllocationHandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,11 @@ TEST_CASE("AllocationHandle Generation creation and validation", "[AllocationHan
REQUIRE(handle.IsValid() == true);
}

SECTION("Max value mask")
{
using TestHandle16_16_0 = Substrate::DefineHandle<16, 0, uint16_t>;
TestHandle16_16_0 handle = TestHandle16_16_0(0);
REQUIRE(handle.GetGenerationMask() == 0xFFFF);
REQUIRE(handle.IsValid() == false);
}
// "Max value mask" (DefineHandle<16, 0, uint16_t>) was removed on 2026-09-01.
// An all-ones generation mask leaves zero index bits, so the handle can address
// nothing and every instance read as invalid. That is now a static_assert in
// BaseHandle -- the instantiation itself is ill-formed, so there is no object
// left to assert against from here. Do not re-add it; it will not compile.

SECTION("Overflow protection")
{
Expand Down Expand Up @@ -139,13 +137,9 @@ TEST_CASE("AllocationHandle Index handling", "[AllocationHandle][Index]")
REQUIRE(handle.GetMaxIndexValue() == 0xFFF);
}

SECTION("Zero index mask")
{
using TestHandle16_16_0 = Substrate::DefineHandle<16, 0, uint16_t>;
TestHandle16_16_0 handle = TestHandle16_16_0(0);
REQUIRE(handle.GetIndexMask() == 0x0000);
REQUIRE(handle.IsValid() == false);
}
// "Zero index mask" removed on 2026-09-01 for the same reason as "Max value mask"
// above: it instantiated DefineHandle<16, 0, uint16_t>, which the BaseHandle
// static_assert now rejects at compile time.

SECTION("Max value index mask")
{
Expand Down
Loading