Files
dl/third/JoltPhysics/Jolt/Core/TempAllocator.h
T
2026-09-16 14:07:40 +08:00

210 lines
5.3 KiB
C++
Vendored

// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/NonCopyable.h>
JPH_NAMESPACE_BEGIN
/// Allocator for temporary allocations.
/// This allocator works as a stack: The blocks must always be freed in the reverse order as they are allocated.
/// Note that allocations and frees can take place from different threads, but the order is guaranteed though
/// job dependencies, so it is not needed to use any form of locking.
class JPH_EXPORT TempAllocator : public NonCopyable
{
public:
JPH_OVERRIDE_NEW_DELETE
/// If this allocator needs to fall back to aligned allocations because JPH_RVECTOR_ALIGNMENT is bigger than the platform default
static constexpr bool needs_aligned_allocate = JPH_RVECTOR_ALIGNMENT > JPH_DEFAULT_ALLOCATE_ALIGNMENT;
/// Destructor
virtual ~TempAllocator() = default;
/// Allocates inSize bytes of memory, returned memory address must be JPH_RVECTOR_ALIGNMENT byte aligned
virtual void * Allocate(uint inSize) = 0;
/// Frees inSize bytes of memory located at inAddress
virtual void Free(void *inAddress, uint inSize) = 0;
};
/// Default implementation of the temp allocator that allocates a large block through malloc upfront
class JPH_EXPORT TempAllocatorImpl final : public TempAllocator
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructs the allocator with a maximum allocatable size of inSize
explicit TempAllocatorImpl(size_t inSize) : mSize(inSize)
{
if constexpr (needs_aligned_allocate)
mBase = static_cast<uint8 *>(AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT));
else
mBase = static_cast<uint8 *>(JPH::Allocate(inSize));
}
/// Destructor, frees the block
virtual ~TempAllocatorImpl() override
{
JPH_ASSERT(mTop == 0);
if constexpr (needs_aligned_allocate)
AlignedFree(mBase);
else
JPH::Free(mBase);
}
// See: TempAllocator
virtual void * Allocate(uint inSize) override
{
if (inSize == 0)
{
return nullptr;
}
else
{
size_t new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
if (new_top > mSize)
{
Trace("TempAllocator: Out of memory trying to allocate %u bytes", inSize);
std::abort();
}
void *address = mBase + mTop;
mTop = new_top;
return address;
}
}
// See: TempAllocator
virtual void Free(void *inAddress, uint inSize) override
{
if (inAddress == nullptr)
{
JPH_ASSERT(inSize == 0);
}
else
{
mTop -= AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
if (mBase + mTop != inAddress)
{
Trace("TempAllocator: Freeing in the wrong order");
std::abort();
}
}
}
/// Check if no allocations have been made
bool IsEmpty() const
{
return mTop == 0;
}
/// Get the total size of the fixed buffer
size_t GetSize() const
{
return mSize;
}
/// Get current usage in bytes of the buffer
size_t GetUsage() const
{
return mTop;
}
/// Check if an allocation of inSize can be made in this fixed buffer allocator
bool CanAllocate(uint inSize) const
{
return mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT) <= mSize;
}
/// Check if memory block at inAddress is owned by this allocator
bool OwnsMemory(const void *inAddress) const
{
return inAddress >= mBase && inAddress < mBase + mSize;
}
private:
uint8 * mBase; ///< Base address of the memory block
size_t mSize; ///< Size of the memory block
size_t mTop = 0; ///< End of currently allocated area
};
/// Implementation of the TempAllocator that just falls back to malloc/free
/// Note: This can be quite slow when running in the debugger as large memory blocks need to be initialized with 0xcd
class JPH_EXPORT TempAllocatorMalloc final : public TempAllocator
{
public:
JPH_OVERRIDE_NEW_DELETE
// See: TempAllocator
virtual void * Allocate(uint inSize) override
{
if (inSize > 0)
{
if constexpr (needs_aligned_allocate)
return AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT);
else
return JPH::Allocate(inSize);
}
else
return nullptr;
}
// See: TempAllocator
virtual void Free(void *inAddress, [[maybe_unused]] uint inSize) override
{
if (inAddress != nullptr)
{
if constexpr (needs_aligned_allocate)
AlignedFree(inAddress);
else
JPH::Free(inAddress);
}
}
};
/// Implementation of the TempAllocator that tries to allocate from a large preallocated block, but falls back to malloc when it is exhausted
class JPH_EXPORT TempAllocatorImplWithMallocFallback final : public TempAllocator
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructs the allocator with an initial fixed block if inSize
explicit TempAllocatorImplWithMallocFallback(uint inSize) :
mAllocator(inSize)
{
}
// See: TempAllocator
virtual void * Allocate(uint inSize) override
{
if (mAllocator.CanAllocate(inSize))
return mAllocator.Allocate(inSize);
else
return mFallbackAllocator.Allocate(inSize);
}
// See: TempAllocator
virtual void Free(void *inAddress, uint inSize) override
{
if (inAddress == nullptr)
{
JPH_ASSERT(inSize == 0);
}
else
{
if (mAllocator.OwnsMemory(inAddress))
mAllocator.Free(inAddress, inSize);
else
mFallbackAllocator.Free(inAddress, inSize);
}
}
private:
TempAllocatorImpl mAllocator;
TempAllocatorMalloc mFallbackAllocator;
};
JPH_NAMESPACE_END