Skip to content

Commit 00a515f

Browse files
committed
feat: more work on allocators
1 parent 26810b6 commit 00a515f

File tree

9 files changed

+847
-1
lines changed

9 files changed

+847
-1
lines changed

engine/foundation/core/core_files.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ set(FILES
127127
private/math/transform.cpp
128128

129129
# private/memory
130+
private/memory/linear_allocator.cpp
130131
private/memory/memory.cpp
131132
private/memory/memory_tracker.cpp
132133
private/memory/mimalloc_allocator.cpp
134+
private/memory/pool_allocator.cpp
135+
private/memory/stack_allocator.cpp
133136
private/memory/std_allocator.cpp
134137

135138
# private/platform
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/memory/linear_allocator.h"
26+
#include "core/memory/memory.h"
27+
#include "core/memory/memory_tracker.h"
28+
29+
30+
//[-------------------------------------------------------]
31+
//[ Namespace ]
32+
//[-------------------------------------------------------]
33+
namespace core {
34+
35+
36+
//[-------------------------------------------------------]
37+
//[ Classes ]
38+
//[-------------------------------------------------------]
39+
LinearAllocator::LinearAllocator(core::sizeT bytes)
40+
: AllocatorImpl()
41+
, mCapacity(bytes)
42+
, mUsed(0) {
43+
mBuffer = static_cast<char*>(Memory::allocate(mCapacity));
44+
}
45+
46+
LinearAllocator::~LinearAllocator() {
47+
Memory::free(mBuffer);
48+
}
49+
50+
void* LinearAllocator::allocate(core::sizeT newNumberOfBytes, core::sizeT alignment) {
51+
// Align the current position
52+
core::sizeT aligned_used = (mUsed + alignment - 1) & ~(alignment - 1);
53+
54+
if (aligned_used + newNumberOfBytes > mCapacity) {
55+
return nullptr; // Out of memory
56+
}
57+
58+
void* result = mBuffer + aligned_used;
59+
mUsed = aligned_used + newNumberOfBytes;
60+
61+
BE_TRACK_ALLOC(result, newNumberOfBytes, "LinearAllocator");
62+
return result;
63+
}
64+
65+
void* LinearAllocator::reallocate(void* oldPointer, core::sizeT oldNumberOfBytes, core::sizeT newNumberOfBytes, core::sizeT alignment) {
66+
// Linear allocator doesn't support reallocation within the buffer
67+
// We need to allocate a new block and copy the data
68+
69+
// Check if pointer is in our buffer range
70+
char* charPtr = static_cast<char*>(oldPointer);
71+
if (charPtr < mBuffer || charPtr >= mBuffer + mCapacity) {
72+
return nullptr; // Not our pointer
73+
}
74+
75+
// If new size is smaller, we can just return the existing pointer
76+
if (newNumberOfBytes <= oldNumberOfBytes) {
77+
return oldPointer;
78+
}
79+
80+
// Otherwise we need to allocate a new block
81+
void* newPtr = allocate(newNumberOfBytes, alignment);
82+
if (newPtr) {
83+
// Copy old data
84+
Memory::copy(newPtr, oldPointer, oldNumberOfBytes);
85+
}
86+
87+
// Don't deallocate old memory - linear allocator doesn't support individual deallocations
88+
return newPtr;
89+
}
90+
91+
void LinearAllocator::deallocate(void* ptr, core::sizeT) {
92+
// Just track the deallocation but don't actually free (will be freed in bulk)
93+
BE_TRACK_DEALLOC(ptr);
94+
}
95+
96+
void LinearAllocator::reset() {
97+
mUsed = 0;
98+
}
99+
100+
core::sizeT LinearAllocator::get_used() const {
101+
return mUsed;
102+
}
103+
104+
core::sizeT LinearAllocator::get_available() const {
105+
return mCapacity - mUsed;
106+
}
107+
108+
109+
//[-------------------------------------------------------]
110+
//[ Namespace ]
111+
//[-------------------------------------------------------]
112+
}

engine/foundation/core/private/memory/memory_tracker.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ void MemoryTracker::leak_report() const {
146146
BE_LOG(Info, "=== No memory leaks detected ===")
147147

148148
for (auto& info: mAllocations) {
149-
String msg = "Unresolved allocation from " + core::to_string(info.second.line) + " in " + String(info.second.file);
149+
String msg = "Unresolved allocation from " + core::to_string(info.second.line) + " in " + String(info.second.function);
150+
BE_LOG(Warning, msg)
150151
}
151152
}
152153
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/memory/pool_allocator.h"
26+
#include "core/memory/memory.h"
27+
#include "core/memory/memory_tracker.h"
28+
29+
30+
//[-------------------------------------------------------]
31+
//[ Namespace ]
32+
//[-------------------------------------------------------]
33+
namespace core {
34+
35+
36+
//[-------------------------------------------------------]
37+
//[ Classes ]
38+
//[-------------------------------------------------------]
39+
PoolAllocator::PoolAllocator(core::sizeT size, core::sizeT chunk_size)
40+
: AllocatorImpl()
41+
, mBlockSize(std::max(size, sizeof(FreeBlock)))
42+
, mBlocksPerChunk(chunk_size)
43+
, mFreeList(nullptr) {
44+
allocate_chunk();
45+
}
46+
47+
PoolAllocator::~PoolAllocator() {
48+
for (void* chunk : mChunks) {
49+
Memory::free(chunk);
50+
}
51+
}
52+
53+
void* PoolAllocator::allocate(core::sizeT newNumberOfBytes, core::sizeT) {
54+
if (newNumberOfBytes > mBlockSize) {
55+
return nullptr; // Size too large for this pool
56+
}
57+
58+
if (!mFreeList) {
59+
allocate_chunk();
60+
}
61+
62+
FreeBlock* block = mFreeList;
63+
mFreeList = mFreeList->next;
64+
65+
BE_TRACK_ALLOC(block, newNumberOfBytes, "PoolAllocator");
66+
return block;
67+
}
68+
69+
void* PoolAllocator::reallocate(void* oldPointer, core::sizeT oldNumberOfBytes, core::sizeT newNumberOfBytes,
70+
core::sizeT alignment) {
71+
// Pool allocator doesn't support reallocation with different sizes
72+
if (newNumberOfBytes <= mBlockSize && oldNumberOfBytes <= mBlockSize) {
73+
// If new size still fits in a block, just return the old pointer
74+
return oldPointer;
75+
}
76+
77+
// If new size doesn't fit, we need to allocate a new block elsewhere
78+
return nullptr; // Indicate reallocation failure, caller should handle this
79+
}
80+
81+
void PoolAllocator::deallocate(void* ptr, core::sizeT) {
82+
BE_TRACK_DEALLOC(ptr);
83+
84+
FreeBlock* block = static_cast<FreeBlock*>(ptr);
85+
block->next = mFreeList;
86+
mFreeList = block;
87+
}
88+
89+
core::sizeT PoolAllocator::get_block_size() const {
90+
return mBlockSize;
91+
}
92+
93+
void PoolAllocator::allocate_chunk() {
94+
char* chunk = static_cast<char*>(Memory::allocate(mBlockSize * mBlocksPerChunk));
95+
if (!chunk) throw std::bad_alloc();
96+
97+
mChunks.push_back(chunk);
98+
99+
// Initialize free list with new blocks
100+
for (core::sizeT i = 0; i < mBlocksPerChunk - 1; ++i) {
101+
FreeBlock* block = reinterpret_cast<FreeBlock*>(chunk + i * mBlockSize);
102+
block->next = reinterpret_cast<FreeBlock*>(chunk + (i + 1) * mBlockSize);
103+
}
104+
105+
// Last block points to current free list
106+
FreeBlock* last_block = reinterpret_cast<FreeBlock*>(chunk + (mBlocksPerChunk - 1) * mBlockSize);
107+
last_block->next = mFreeList;
108+
mFreeList = reinterpret_cast<FreeBlock*>(chunk);
109+
}
110+
111+
112+
//[-------------------------------------------------------]
113+
//[ Namespace ]
114+
//[-------------------------------------------------------]
115+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/memory/stack_allocator.h"
26+
#include "core/memory/memory.h"
27+
#include "core/memory/memory_tracker.h"
28+
29+
30+
//[-------------------------------------------------------]
31+
//[ Namespace ]
32+
//[-------------------------------------------------------]
33+
namespace core {
34+
35+
36+
//[-------------------------------------------------------]
37+
//[ Classes ]
38+
//[-------------------------------------------------------]
39+
StackAllocator::StackAllocator(core::sizeT bytes)
40+
: AllocatorImpl()
41+
, mCapacity(bytes)
42+
, mUsed(0) {
43+
mBuffer = static_cast<char*>(Memory::allocate(mCapacity));
44+
}
45+
46+
StackAllocator::~StackAllocator() {
47+
Memory::free(mBuffer);
48+
}
49+
50+
void* StackAllocator::allocate(core::sizeT newNumberOfBytes, core::sizeT alignment) {
51+
// Align the current position
52+
core::sizeT aligned_used = (mUsed + alignment - 1) & ~(alignment - 1);
53+
54+
if (aligned_used + newNumberOfBytes > mCapacity) {
55+
return nullptr; // Out of memory
56+
}
57+
58+
void* result = mBuffer + aligned_used;
59+
mUsed = aligned_used + newNumberOfBytes;
60+
61+
BE_TRACK_ALLOC(result, newNumberOfBytes, "StackAllocator");
62+
return result;
63+
}
64+
65+
void* StackAllocator::reallocate(void* oldPointer, core::sizeT oldNumberOfBytes, core::sizeT newNumberOfBytes,
66+
core::sizeT alignment) {
67+
// Check if this is the top of the stack (the most recent allocation)
68+
char* oldCharPtr = static_cast<char*>(oldPointer);
69+
core::sizeT old_offset = oldCharPtr - mBuffer;
70+
71+
// If this is the most recent allocation and we're growing, we can just extend it
72+
if (old_offset + oldNumberOfBytes == mUsed && newNumberOfBytes > oldNumberOfBytes) {
73+
core::sizeT additionalBytes = newNumberOfBytes - oldNumberOfBytes;
74+
if (mUsed + additionalBytes <= mCapacity) {
75+
mUsed += additionalBytes;
76+
BE_TRACK_DEALLOC(oldPointer);
77+
BE_TRACK_ALLOC(oldPointer, newNumberOfBytes, "StackAllocator");
78+
return oldPointer;
79+
}
80+
}
81+
82+
// Otherwise, we need to allocate a new block and copy
83+
void* newPtr = allocate(newNumberOfBytes, alignment);
84+
if (newPtr && oldPointer) {
85+
// Copy the old data
86+
Memory::copy(newPtr, oldPointer, std::min(oldNumberOfBytes, newNumberOfBytes));
87+
88+
// Don't actually deallocate in a stack allocator
89+
BE_TRACK_DEALLOC(oldPointer);
90+
}
91+
92+
return newPtr;
93+
}
94+
95+
void StackAllocator::deallocate(void* ptr, core::sizeT size) {
96+
// Just track the deallocation
97+
BE_TRACK_DEALLOC(ptr);
98+
99+
// If this is the top of the stack, we can actually reclaim the space
100+
char* charPtr = static_cast<char*>(ptr);
101+
core::sizeT offset = charPtr - mBuffer;
102+
if (offset + size == mUsed) {
103+
mUsed = offset;
104+
}
105+
// Otherwise, memory will be reclaimed when marker is rolled back or allocator is reset
106+
}
107+
108+
StackAllocator::Marker StackAllocator::get_marker() const {
109+
return Marker{mUsed};
110+
}
111+
112+
void StackAllocator::roll_back(Marker marker) {
113+
// When rolling back, notify the tracker about the deallocations
114+
if (marker.position < mUsed) {
115+
// This is a simplification - ideally we'd track each allocation individually
116+
BE_TRACK_DEALLOC(mBuffer + marker.position);
117+
}
118+
mUsed = marker.position;
119+
}
120+
121+
core::sizeT StackAllocator::get_used() const {
122+
return mUsed;
123+
}
124+
125+
core::sizeT StackAllocator::get_available() const {
126+
return mCapacity - mUsed;
127+
}
128+
129+
130+
//[-------------------------------------------------------]
131+
//[ Namespace ]
132+
//[-------------------------------------------------------]
133+
}

engine/foundation/core/public/core/main.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
//[ Includes ]
3030
//[-------------------------------------------------------]
3131
#include "core/container/vector.h"
32+
#include "core/memory/memory_tracker.h"
3233
#include "core/string/string.h"
3334
#if defined(WIN32)
3435
#endif

0 commit comments

Comments
 (0)