Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
chunk_allocator.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cinttypes>
5#include <cstdint>
6#include <cstring>
7
8#include "gaia/cnt/fwd_llist.h"
9#include "gaia/cnt/sarray.h"
10#include "gaia/core/bit_utils.h"
11#include "gaia/core/dyn_singleton.h"
12#include "gaia/core/utility.h"
13#include "gaia/mem/mem_alloc.h"
14#include "gaia/util/logging.h"
15
16namespace gaia {
17 namespace ecs {
19 namespace detail {
20 struct MemoryBlockHeader final {
21 uintptr_t m_pageAddr = 0;
22 uint32_t m_reserved = 0;
23#if GAIA_DEBUG
24 uint32_t m_requestedBytes = 0;
25#endif
26 };
27
28 class ChunkAllocatorImpl;
29 } // namespace detail
31
33 static constexpr uint32_t MemoryBlockAlignment = 64;
35 static constexpr uint32_t MinMemoryBlockSize = 1024 * 8;
37 static constexpr uint32_t MemoryBlockSizeClasses = 4;
39 static constexpr uint32_t MaxMemoryBlockSize = UINT16_MAX & ~(MemoryBlockAlignment - 1);
42 static_assert(sizeof(uintptr_t) == 4 || sizeof(uintptr_t) == 8);
43 static constexpr uint32_t MemoryBlockUsableOffset = sizeof(uintptr_t) == 4 ? 12 : 40;
44
48 constexpr uint16_t mem_block_size(uint32_t sizeType) {
49 constexpr uint16_t sizes[] = {
50 MinMemoryBlockSize, MinMemoryBlockSize * 2, MinMemoryBlockSize * 4, MaxMemoryBlockSize};
51 return sizes[sizeType];
52 }
53
57 constexpr uint8_t mem_block_size_type(uint32_t sizeBytes) {
58 GAIA_ASSERT(sizeBytes > 0);
59 if (sizeBytes <= MinMemoryBlockSize)
60 return 0;
61 if (sizeBytes <= MinMemoryBlockSize * 2)
62 return 1;
63 if (sizeBytes <= MinMemoryBlockSize * 4)
64 return 2;
65 return 3;
66 }
67
68#if GAIA_ECS_CHUNK_ALLOCATOR
69 struct GAIA_API ChunkAllocatorPageStats final {
71 uint64_t mem_total;
73 uint64_t mem_used;
75 uint32_t num_pages;
77 uint32_t num_pages_free;
78 #if GAIA_DEBUG
80 uint64_t mem_requested;
82 uint32_t num_pages_empty;
83 #endif
84 };
85
86 struct GAIA_API ChunkAllocatorStats final {
87 ChunkAllocatorPageStats stats[MemoryBlockSizeClasses];
88 };
89
90 using ChunkAllocator = core::dyn_singleton<detail::ChunkAllocatorImpl>;
91
93 namespace detail {
94 static_assert(sizeof(MemoryBlockHeader) <= MemoryBlockUsableOffset);
95
96 struct MemoryPageHeader {
98 void* m_data;
99
100 MemoryPageHeader(void* ptr): m_data(ptr) {}
101 };
102
103 struct MemoryPage: MemoryPageHeader, cnt::fwd_llist_base<MemoryPage> {
104 static constexpr uint16_t NBlocks = 48;
105 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
106 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
107 #if GAIA_DEBUG
108 static constexpr uint8_t FreedBlockPattern = 0xDD;
109 #endif
110 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
111 using BlockArray = cnt::sarray<uint8_t, BlockArrayBytes>;
112 using BitView = core::bit_view<NBlocks_Bits>;
113
115 BlockArray m_blocks;
116
118 uint32_t m_sizeType : 2;
120 uint32_t m_blockCnt : NBlocks_Bits;
122 uint32_t m_usedBlocks : NBlocks_Bits;
124 uint32_t m_nextFreeBlock : NBlocks_Bits;
126 uint32_t m_freeBlocks : NBlocks_Bits;
128 // uint32_t m_unused : 6;
129
130 #if GAIA_ASSERT_ENABLED
131 uint64_t m_usedMask = 0;
132 #endif
133
134 MemoryPage(void* ptr, uint8_t sizeType):
135 MemoryPageHeader(ptr), m_sizeType(sizeType), m_blockCnt(0), m_usedBlocks(0), m_nextFreeBlock(0),
136 m_freeBlocks(0) {
137 #if GAIA_ASSERT_ENABLED
138 static_assert(sizeof(MemoryPage) <= 72);
139 #else
140 static_assert(sizeof(MemoryPage) <= 64);
141 #endif
142 }
143
144 void write_block_idx(uint32_t blockIdx, uint32_t value) {
145 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
146
147 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
148 GAIA_ASSERT(value <= InvalidBlockId);
149
150 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
151 }
152
153 uint8_t read_block_idx(uint32_t blockIdx) const {
154 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
155
156 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
157
158 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
159 }
160
161 GAIA_NODISCARD void* alloc_block(
162 #if GAIA_DEBUG
163 uint32_t bytesWanted
164 #endif
165 ) {
166 auto StoreBlockAddress = [&](uint32_t index) {
167 // Encode info about chunk's page in the memory block.
168 // The actual pointer returned is offset by MemoryBlockUsableOffset bytes
169 auto* pMemoryBlock = (uint8_t*)m_data + (index * mem_block_size(m_sizeType));
170 GAIA_ASSERT((uintptr_t)pMemoryBlock % MemoryBlockAlignment == 0);
171 auto& header = block_header(pMemoryBlock);
172 header.m_pageAddr = (uintptr_t)this;
173 header.m_reserved = 0;
174 #if GAIA_DEBUG
175 header.m_requestedBytes = bytesWanted;
176 #endif
177 return (void*)(pMemoryBlock + MemoryBlockUsableOffset);
178 };
179
180 // We don't want to go out of range for new blocks
181 GAIA_ASSERT(!full() && "Trying to allocate too many blocks!");
182
183 uint32_t index = 0;
184 if (m_freeBlocks == 0U) {
185 index = m_blockCnt;
186 ++m_usedBlocks;
187 ++m_blockCnt;
188 write_block_idx(index, index);
189 } else {
190 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt && "Block allocator recycle list broken!");
191
192 ++m_usedBlocks;
193 --m_freeBlocks;
194
195 index = m_nextFreeBlock;
196 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
197 }
198
199 #if GAIA_ASSERT_ENABLED
200 GAIA_ASSERT((m_usedMask & (uint64_t(1) << index)) == 0 && "Block already marked as live");
201 m_usedMask |= uint64_t(1) << index;
202 #endif
203
204 return StoreBlockAddress(index);
205 }
206
207 void free_block(void* pBlock) {
208 GAIA_ASSERT(pBlock != nullptr);
209 GAIA_ASSERT(m_usedBlocks > 0);
210 GAIA_ASSERT(m_freeBlocks <= NBlocks);
211
212 // Offset the chunk memory so we get the real block address
213 const auto* pMemoryBlock = (uint8_t*)pBlock - MemoryBlockUsableOffset;
214 const auto blckAddr = (uintptr_t)pMemoryBlock;
215 GAIA_ASSERT(blckAddr % MemoryBlockAlignment == 0);
216 const auto dataAddr = (uintptr_t)m_data;
217 GAIA_ASSERT(blckAddr >= dataAddr);
218 const auto blockSize = (uintptr_t)mem_block_size(m_sizeType);
219 #if GAIA_ASSERT_ENABLED
220 const auto pageSize = blockSize * NBlocks;
221 GAIA_ASSERT(blckAddr < dataAddr + pageSize);
222 #endif
223 GAIA_ASSERT((blckAddr - dataAddr) % blockSize == 0);
224 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / blockSize);
225 GAIA_ASSERT(blockIdx < m_blockCnt);
226
227 #if GAIA_DEBUG
228 auto& header = block_header((void*)pMemoryBlock);
229 GAIA_ASSERT(header.m_requestedBytes > 0);
230 #endif
231 #if GAIA_ASSERT_ENABLED
232 GAIA_ASSERT((m_usedMask & (uint64_t(1) << blockIdx)) != 0 && "Double free or corrupted block state");
233 m_usedMask &= ~(uint64_t(1) << blockIdx);
234 #endif
235
236 #if GAIA_DEBUG
237 header.m_requestedBytes = 0;
238 std::memset(pBlock, FreedBlockPattern, blockSize - MemoryBlockUsableOffset);
239 #endif
240
241 // Update our implicit list
242 if (m_freeBlocks == 0U)
243 write_block_idx(blockIdx, InvalidBlockId);
244 else
245 write_block_idx(blockIdx, m_nextFreeBlock);
246 m_nextFreeBlock = blockIdx;
247
248 ++m_freeBlocks;
249 --m_usedBlocks;
250 }
251
252 GAIA_NODISCARD uint32_t used_blocks_cnt() const {
253 return m_usedBlocks;
254 }
255
256 GAIA_NODISCARD bool full() const {
257 return used_blocks_cnt() >= NBlocks;
258 }
259
260 GAIA_NODISCARD bool empty() const {
261 return used_blocks_cnt() == 0;
262 }
263
264 void verify() const {
265 #if GAIA_ASSERT_ENABLED
266 GAIA_ASSERT(m_sizeType < MemoryBlockSizeClasses);
267 GAIA_ASSERT(m_blockCnt <= NBlocks);
268 GAIA_ASSERT(m_usedBlocks <= m_blockCnt);
269 GAIA_ASSERT(m_freeBlocks <= m_blockCnt);
270 GAIA_ASSERT(m_usedBlocks + m_freeBlocks == m_blockCnt);
271
272 const auto blockSize = (uintptr_t)mem_block_size(m_sizeType);
273
274 const auto pageAddr = (uintptr_t)m_data;
275 GAIA_ASSERT(pageAddr % MemoryBlockAlignment == 0);
276
277 #if GAIA_DEBUG
278 uint64_t freeMask = 0;
279 #endif
280
281 if (m_freeBlocks != 0) {
282 uint32_t next = m_nextFreeBlock;
283 GAIA_FOR(m_freeBlocks) {
284 GAIA_ASSERT(next < m_blockCnt);
285 #if GAIA_DEBUG
286 const auto bit = uint64_t(1) << next;
287 GAIA_ASSERT((freeMask & bit) == 0 && "Free list contains a cycle");
288 freeMask |= bit;
289 #endif
290 next = read_block_idx(next);
291 }
292
293 GAIA_ASSERT(next == InvalidBlockId);
294 }
295
296 GAIA_FOR(m_blockCnt) {
297 const auto* pMemoryBlock = (const uint8_t*)m_data + (i * blockSize);
298 const auto& header = block_header(pMemoryBlock);
299 GAIA_ASSERT(header.m_pageAddr == (uintptr_t)this);
300 GAIA_ASSERT(((uintptr_t)pMemoryBlock % MemoryBlockAlignment) == 0);
301
302 #if GAIA_DEBUG
303 const bool isFree = (freeMask & (uint64_t(1) << i)) != 0;
304 GAIA_ASSERT((header.m_requestedBytes == 0) == isFree);
305 #endif
306 }
307
308 #if GAIA_DEBUG
309 GAIA_ASSERT((m_usedMask & freeMask) == 0);
310 const auto liveMask = m_blockCnt == 64 ? ~uint64_t(0) : ((uint64_t(1) << m_blockCnt) - 1);
311 GAIA_ASSERT((m_usedMask | freeMask) == liveMask);
312 #endif
313 #endif
314 }
315
316 #if GAIA_DEBUG
317 GAIA_NODISCARD uint64_t requested_bytes() const {
318 if (m_usedBlocks == 0)
319 return 0;
320
321 uint64_t freeMask = 0;
322 uint32_t next = m_nextFreeBlock;
323 GAIA_FOR(m_freeBlocks) {
324 GAIA_ASSERT(next < m_blockCnt);
325 const auto bit = uint64_t(1) << next;
326 GAIA_ASSERT((freeMask & bit) == 0 && "Free list contains a cycle");
327 freeMask |= bit;
328 next = read_block_idx(next);
329 }
330
331 uint64_t requested = 0;
332 GAIA_FOR(m_blockCnt) {
333 if ((freeMask & (uint64_t(1) << i)) != 0)
334 continue;
335
336 const auto* pMemoryBlock = (const uint8_t*)m_data + (i * mem_block_size(m_sizeType));
337 requested += block_header(pMemoryBlock).m_requestedBytes;
338 }
339
340 return requested;
341 }
342 #endif
343
344 private:
345 static MemoryBlockHeader& block_header(void* pMemoryBlock) {
346 return *(MemoryBlockHeader*)pMemoryBlock;
347 }
348
349 static const MemoryBlockHeader& block_header(const void* pMemoryBlock) {
350 return *(const MemoryBlockHeader*)pMemoryBlock;
351 }
352 };
353
354 enum class MemoryPageState : uint8_t { Detached, Empty, Partial, Full };
355
356 struct MemoryPageContainer {
358 cnt::fwd_llist<MemoryPage> pagesEmpty;
360 cnt::fwd_llist<MemoryPage> pagesPartial;
362 cnt::fwd_llist<MemoryPage> pagesFull;
363
364 GAIA_NODISCARD bool empty() const {
365 return pagesEmpty.empty() && pagesPartial.empty() && pagesFull.empty();
366 }
367 };
368
370 class ChunkAllocatorImpl {
371 friend ::gaia::ecs::ChunkAllocator;
372
374 MemoryPageContainer m_pages[MemoryBlockSizeClasses];
375
377 bool m_isDone = false;
378
379 private:
380 ChunkAllocatorImpl() = default;
381
382 void on_delete() {
383 flush(true);
384
385 // Make sure there are no leaks
386 auto memStats = stats();
387 for (const auto& s: memStats.stats) {
388 if (s.mem_total != 0) {
389 GAIA_ASSERT2(false, "ECS leaking memory");
390 GAIA_LOG_W("ECS leaking memory!");
391 diag();
392 }
393 }
394 }
395
396 public:
397 ~ChunkAllocatorImpl() {
398 on_delete();
399 }
400
401 ChunkAllocatorImpl(ChunkAllocatorImpl&& world) = delete;
402 ChunkAllocatorImpl(const ChunkAllocatorImpl& world) = delete;
403 ChunkAllocatorImpl& operator=(ChunkAllocatorImpl&&) = delete;
404 ChunkAllocatorImpl& operator=(const ChunkAllocatorImpl&) = delete;
405
407 void* alloc(uint32_t bytesWanted) {
408 GAIA_ASSERT(bytesWanted > 0);
409 GAIA_ASSERT(bytesWanted <= MaxMemoryBlockSize);
410 if (bytesWanted == 0 || bytesWanted > MaxMemoryBlockSize)
411 return nullptr;
412
413 const ::gaia::mem::detail::ArenaLock arenaLock;
414 const auto sizeType = mem_block_size_type(bytesWanted);
415 auto& container = m_pages[sizeType];
416
417 MemoryPageState prevState = MemoryPageState::Partial;
418 auto* pPage = container.pagesPartial.first;
419 if (pPage == nullptr) {
420 prevState = MemoryPageState::Empty;
421 pPage = container.pagesEmpty.first;
422 if (pPage == nullptr) {
423 prevState = MemoryPageState::Detached;
424 pPage = alloc_page(sizeType);
425 }
426 }
427
428 // Allocate a new chunk of memory
429 #if GAIA_DEBUG
430 void* pBlock = pPage->alloc_block(bytesWanted);
431 #else
432 void* pBlock = pPage->alloc_block();
433 #endif
434
435 move_page(container, pPage, prevState, state_for(*pPage));
436 verify();
437 return pBlock;
438 }
439
440 GAIA_CLANG_WARNING_PUSH()
441 // Memory is aligned so we can silence this warning
442 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
443
445 void free(void* pBlock) {
446 GAIA_ASSERT(pBlock != nullptr);
447 if (pBlock == nullptr)
448 return;
449
450 const ::gaia::mem::detail::ArenaLock arenaLock;
451 // Decode the page from the address
452 const auto& header = *(const MemoryBlockHeader*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
453 const auto pageAddr = header.m_pageAddr;
454 GAIA_ASSERT(pageAddr % sizeof(uintptr_t) == 0);
455 #if GAIA_DEBUG
456 GAIA_ASSERT(header.m_requestedBytes > 0);
457 #endif
458 auto* pPage = (MemoryPage*)pageAddr;
459 const auto prevState = state_for(*pPage);
460
461 auto& container = m_pages[pPage->m_sizeType];
462
463 #if GAIA_ASSERT_ENABLED
464 if (prevState == MemoryPageState::Full) {
465 const auto res = container.pagesFull.has(pPage);
466 GAIA_ASSERT(res && "Memory page couldn't be found among full pages");
467 } else if (prevState == MemoryPageState::Partial) {
468 const auto res = container.pagesPartial.has(pPage);
469 GAIA_ASSERT(res && "Memory page couldn't be found among partial pages");
470 } else {
471 GAIA_ASSERT(false && "Allocated block can't belong to an empty page");
472 }
473 #endif
474
475 // Free the chunk
476 pPage->free_block(pBlock);
477
478 // Update lists
479 move_page(container, pPage, prevState, state_for(*pPage));
480 verify();
481
482 // Special handling for the allocator signaled to destroy itself
483 if (m_isDone) {
484 if (pPage->empty()) {
485 container.pagesEmpty.unlink(pPage);
486 free_page(pPage);
487 }
488
489 try_delete_this();
490 }
491 }
492
493 GAIA_CLANG_WARNING_POP()
494
495
496 ChunkAllocatorStats stats() const {
497 ChunkAllocatorStats stats{};
498 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
499 stats.stats[sizeType] = page_stats(sizeType);
500 return stats;
501 }
502
505 void flush(bool releaseAll = false) {
506 const ::gaia::mem::detail::ArenaLock arenaLock;
507 uint32_t i = 0;
508 for (auto& page: m_pages)
509 flushPages(page, i++, releaseAll);
510 verify();
511 }
512
514 void diag() const {
515 auto diagPage = [](const ChunkAllocatorPageStats& stats, uint32_t sizeType) {
516 GAIA_LOG_N("ChunkAllocator %uK stats", mem_block_size(sizeType) / 1024);
517 GAIA_LOG_N(" Allocated: %" PRIu64 " B", stats.mem_total);
518 GAIA_LOG_N(" Reserved by live blocks: %" PRIu64 " B", stats.mem_used);
519 GAIA_LOG_N(" Pages: %u", stats.num_pages);
520 GAIA_LOG_N(" Reusable pages: %u", stats.num_pages_free);
521 #if !GAIA_DEBUG
522 GAIA_LOG_N(
523 " Utilization: %.1f%%",
524 stats.mem_total ? 100.0 * ((double)stats.mem_used / (double)stats.mem_total) : 0);
525 #else
526 GAIA_LOG_N(" Requested: %" PRIu64 " B", stats.mem_requested);
527 GAIA_LOG_N(" Free capacity: %" PRIu64 " B", stats.mem_total - stats.mem_used);
528 GAIA_LOG_N(" Internal slack: %" PRIu64 " B", stats.mem_used - stats.mem_requested);
529 GAIA_LOG_N(
530 " Utilization: %.1f%%",
531 stats.mem_total ? 100.0 * ((double)stats.mem_requested / (double)stats.mem_total) : 0);
532 GAIA_LOG_N(" Empty pages: %u", stats.num_pages_empty);
533 #endif
534 };
535
536 auto memStats = stats();
537 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
538 diagPage(memStats.stats[sizeType], sizeType);
539 }
540
541 void verify() const {
542 #if GAIA_ASSERT_ENABLED
543 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
544 verify_container(m_pages[sizeType], sizeType);
545 #endif
546 }
547
548 private:
549 static constexpr const char* s_strChunkAlloc_Chunk = "Chunk";
550 static constexpr const char* s_strChunkAlloc_MemPage = "MemoryPage";
551
552 static MemoryPage* alloc_page(uint8_t sizeType) {
553 const uint32_t size = mem_block_size(sizeType) * MemoryPage::NBlocks;
554 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(s_strChunkAlloc_Chunk, MemoryBlockAlignment, size);
555 auto* pMemoryPage = mem::AllocHelper::alloc<MemoryPage>(s_strChunkAlloc_MemPage);
556 return new (pMemoryPage) MemoryPage(pPageData, sizeType);
557 }
558
559 static void free_page(MemoryPage* pMemoryPage) {
560 GAIA_ASSERT(pMemoryPage != nullptr);
561
562 mem::AllocHelper::free_alig(s_strChunkAlloc_Chunk, pMemoryPage->m_data);
563 pMemoryPage->~MemoryPage();
564 mem::AllocHelper::free(s_strChunkAlloc_MemPage, pMemoryPage);
565 }
566
567 void done() {
568 m_isDone = true;
569 }
570
571 void try_delete_this() {
572 // When there is nothing left, delete the allocator
573 bool allEmpty = true;
574 for (const auto& c: m_pages)
575 allEmpty = allEmpty && c.empty();
576 if (allEmpty)
577 delete this;
578 }
579
580 static constexpr uint32_t warm_pages_to_keep(uint32_t sizeType) {
581 constexpr uint8_t WarmPagesPerSizeClass[] = {1, 1, 0, 0};
582 return WarmPagesPerSizeClass[sizeType];
583 }
584
585 static MemoryPageState state_for(const MemoryPage& page) {
586 if (page.empty())
587 return MemoryPageState::Empty;
588 if (page.full())
589 return MemoryPageState::Full;
590 return MemoryPageState::Partial;
591 }
592
593 static cnt::fwd_llist<MemoryPage>& page_list(MemoryPageContainer& container, MemoryPageState state) {
594 switch (state) {
595 case MemoryPageState::Empty:
596 return container.pagesEmpty;
597 case MemoryPageState::Partial:
598 return container.pagesPartial;
599 default:
600 GAIA_ASSERT(state == MemoryPageState::Full);
601 return container.pagesFull;
602 }
603 }
604
605 static void move_page(
606 MemoryPageContainer& container, MemoryPage* pPage, MemoryPageState fromState, MemoryPageState toState) {
607 if (fromState == toState)
608 return;
609
610 if (fromState != MemoryPageState::Detached)
611 page_list(container, fromState).unlink(pPage);
612 page_list(container, toState).link(pPage);
613 }
614
615 [[maybe_unused]] static void verify_page_membership(
616 [[maybe_unused]] const MemoryPageContainer& container, //
617 [[maybe_unused]] const MemoryPage& page, //
618 [[maybe_unused]] MemoryPageState expectedState //
619 ) {
620 (void)container;
621 GAIA_ASSERT(state_for(page) == expectedState);
622 GAIA_ASSERT(page.get_fwd_llist_link().linked());
623 }
624
625 static void verify_container(const MemoryPageContainer& container, uint32_t sizeType) {
626 (void)sizeType;
627 for (const auto& page: container.pagesEmpty) {
628 GAIA_ASSERT(page.m_sizeType == sizeType);
629 verify_page_membership(container, page, MemoryPageState::Empty);
630 page.verify();
631 }
632
633 for (const auto& page: container.pagesPartial) {
634 GAIA_ASSERT(page.m_sizeType == sizeType);
635 verify_page_membership(container, page, MemoryPageState::Partial);
636 page.verify();
637 }
638
639 for (const auto& page: container.pagesFull) {
640 GAIA_ASSERT(page.m_sizeType == sizeType);
641 verify_page_membership(container, page, MemoryPageState::Full);
642 page.verify();
643 }
644 }
645
646 ChunkAllocatorPageStats page_stats(uint32_t sizeType) const {
647 ChunkAllocatorPageStats stats{};
648 const auto& container = m_pages[sizeType];
649 const auto blockSize = (uint64_t)mem_block_size(sizeType);
650 const auto pageSize = blockSize * MemoryPage::NBlocks;
651
652 stats.num_pages = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size() +
653 (uint32_t)container.pagesFull.size();
654 stats.num_pages_free = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size();
655 stats.mem_total = stats.num_pages * pageSize;
656 stats.mem_used = container.pagesFull.size() * pageSize;
657
658 #if GAIA_DEBUG
659 stats.num_pages_empty = (uint32_t)container.pagesEmpty.size();
660
661 for (const auto& page: container.pagesFull)
662 stats.mem_requested += page.requested_bytes();
663
664 for (const auto& page: container.pagesPartial) {
665 stats.mem_used += page.used_blocks_cnt() * blockSize;
666 stats.mem_requested += page.requested_bytes();
667 }
668 #else
669 for (const auto& page: container.pagesPartial)
670 stats.mem_used += page.used_blocks_cnt() * blockSize;
671 #endif
672
673 return stats;
674 }
675
678 void flushPages(MemoryPageContainer& container, uint32_t sizeType, bool releaseAll) {
679 const bool keepWarmPage = !releaseAll && warm_pages_to_keep(sizeType) != 0;
680 bool keptWarmPage = false;
681 for (auto it = container.pagesEmpty.begin(); it != container.pagesEmpty.end();) {
682 auto* pPage = &(*it);
683 ++it;
684
685 // Skip non-empty pages
686 if (!pPage->empty())
687 continue;
688
689 if (keepWarmPage && !keptWarmPage) {
690 keptWarmPage = true;
691 continue;
692 }
693
694 container.pagesEmpty.unlink(pPage);
695 free_page(pPage);
696 }
697 }
698 };
699 } // namespace detail
701
702#endif
703
704 } // namespace ecs
705} // namespace gaia