mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 06:08:35 +00:00
178 lines
4.8 KiB
C++
178 lines
4.8 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/memorypool.h"
|
|
#include "common/util.h"
|
|
|
|
namespace Common {
|
|
|
|
enum {
|
|
INITIAL_CHUNKS_PER_PAGE = 8
|
|
};
|
|
|
|
static size_t adjustChunkSize(size_t chunkSize) {
|
|
// You must at least fit the pointer in the node (technically unneeded considering the next rounding statement)
|
|
chunkSize = MAX(chunkSize, sizeof(void *));
|
|
// There might be an alignment problem on some platforms when trying to load a void* on a non natural boundary
|
|
// so we round to the next sizeof(void *)
|
|
chunkSize = (chunkSize + sizeof(void *) - 1) & (~(sizeof(void *) - 1));
|
|
|
|
return chunkSize;
|
|
}
|
|
|
|
|
|
MemoryPool::MemoryPool(size_t chunkSize)
|
|
: _chunkSize(adjustChunkSize(chunkSize)) {
|
|
|
|
_next = nullptr;
|
|
|
|
_chunksPerPage = INITIAL_CHUNKS_PER_PAGE;
|
|
}
|
|
|
|
MemoryPool::~MemoryPool() {
|
|
#if 0
|
|
freeUnusedPages();
|
|
if (!_pages.empty())
|
|
warning("Memory leak found in pool");
|
|
#endif
|
|
|
|
for (size_t i = 0; i < _pages.size(); ++i)
|
|
::free(_pages[i].start);
|
|
}
|
|
|
|
void MemoryPool::allocPage() {
|
|
Page page;
|
|
|
|
// Allocate a new page
|
|
page.numChunks = _chunksPerPage;
|
|
assert(page.numChunks * _chunkSize < 16*1024*1024); // Refuse to allocate pages bigger than 16 MB
|
|
|
|
page.start = ::malloc(page.numChunks * _chunkSize);
|
|
assert(page.start);
|
|
_pages.push_back(page);
|
|
|
|
|
|
// Next time, we'll allocate a page twice as big as this one.
|
|
_chunksPerPage *= 2;
|
|
|
|
// Add the page to the pool of free chunk
|
|
addPageToPool(page);
|
|
}
|
|
|
|
void MemoryPool::addPageToPool(const Page &page) {
|
|
// Add all chunks of the new page to the linked list (pool) of free chunks
|
|
void *current = page.start;
|
|
for (size_t i = 1; i < page.numChunks; ++i) {
|
|
void *next = (byte *)current + _chunkSize;
|
|
*(void **)current = next;
|
|
|
|
current = next;
|
|
}
|
|
|
|
// Last chunk points to the old _next
|
|
*(void **)current = _next;
|
|
|
|
// From now on, the first free chunk is the first chunk of the new page
|
|
_next = page.start;
|
|
}
|
|
|
|
void *MemoryPool::allocChunk() {
|
|
// No free chunks left? Allocate a new page
|
|
if (!_next)
|
|
allocPage();
|
|
|
|
assert(_next);
|
|
void *result = _next;
|
|
_next = *(void **)result;
|
|
return result;
|
|
}
|
|
|
|
void MemoryPool::freeChunk(void *ptr) {
|
|
// Add the chunk back to (the start of) the list of free chunks
|
|
*(void **)ptr = _next;
|
|
_next = ptr;
|
|
}
|
|
|
|
// Technically not compliant C++ to compare unrelated pointers. In practice...
|
|
bool MemoryPool::isPointerInPage(void *ptr, const Page &page) {
|
|
return (ptr >= page.start) && (ptr < (char *)page.start + page.numChunks * _chunkSize);
|
|
}
|
|
|
|
void MemoryPool::freeUnusedPages() {
|
|
//std::sort(_pages.begin(), _pages.end());
|
|
Array<size_t> numberOfFreeChunksPerPage;
|
|
numberOfFreeChunksPerPage.resize(_pages.size());
|
|
for (size_t i = 0; i < numberOfFreeChunksPerPage.size(); ++i) {
|
|
numberOfFreeChunksPerPage[i] = 0;
|
|
}
|
|
|
|
// Compute for each page how many chunks in it are still in use.
|
|
void *iterator = _next;
|
|
while (iterator) {
|
|
// TODO: This should be a binary search (requiring us to keep _pages sorted)
|
|
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
if (isPointerInPage(iterator, _pages[i])) {
|
|
++numberOfFreeChunksPerPage[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
iterator = *(void **)iterator;
|
|
}
|
|
|
|
// Free all pages which are not in use.
|
|
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
if (numberOfFreeChunksPerPage[i] == _pages[i].numChunks) {
|
|
// Remove all chunks of this page from the list of free chunks
|
|
void **iter2 = &_next;
|
|
while (*iter2) {
|
|
if (isPointerInPage(*iter2, _pages[i]))
|
|
*iter2 = **(void ***)iter2;
|
|
else
|
|
iter2 = *(void ***)iter2;
|
|
}
|
|
|
|
::free(_pages[i].start);
|
|
_pages[i].start = nullptr;
|
|
}
|
|
}
|
|
|
|
// Remove all now unused pages
|
|
size_t newSize = 0;
|
|
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
if (_pages[i].start != nullptr) {
|
|
if (newSize != i)
|
|
_pages[newSize] = _pages[i];
|
|
++newSize;
|
|
}
|
|
}
|
|
_pages.resize(newSize);
|
|
|
|
// Reset _chunksPerPage
|
|
_chunksPerPage = INITIAL_CHUNKS_PER_PAGE;
|
|
for (size_t i = 0; i < _pages.size(); ++i) {
|
|
if (_chunksPerPage < _pages[i].numChunks)
|
|
_chunksPerPage = _pages[i].numChunks;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Common
|