Files
scummvm/sword2/memory.cpp
T
Torbjörn Andersson e6f19ff83a Much like an early civilization with no concept of the number zero, the
new memory manager didn't have the concept of the NULL pointer. Now it
does.

If ScummVM ever crashed for you when using the phone early in the game,
this patch hopefully fixes that bug. (If it didn't crash for you, memory
block zero was still allocated, so 0 still decoded to a valid pointer.)

svn-id: r14937
2004-09-07 06:29:57 +00:00

261 lines
6.9 KiB
C++

/* Copyright (C) 1994-2004 Revolution Software Ltd
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*/
// The new memory manager, now only used by the resource manager. The original
// one would allocated a 12 MB memory pool at startup, which may have been
// appropriate for the original Playstation version but didn't work very well
// with our PocketPC version.
//
// There is one thing that prevents us from replacing the whole memory manager
// with the standard memory allocation functions: Broken Sword II absolutely,
// positively needs to be able to encode pointers as 32-bit integers. The
// original engine did this simply by casting between pointers and integers,
// but as far as I know that's not a very portable thing to do.
//
// If it had only used pointers as opcode parameters it would have been
// possible, albeit messy, to extend the stack data type. However, there is
// code in walker.cpp that obviously violates that assumption, and there are
// probably other cases as well.
//
// Instead, we take advantage of the fact that the original memory manager
// could only handle up to 999 blocks of memory. That means we can encode a
// pointer as a 10-bit id and a 22-bit offset into the block. Judging by early
// testing, both should be plenty.
//
// The number zero is used to represent the NULL pointer.
#include "common/stdafx.h"
#include "sword2/sword2.h"
#include "sword2/console.h"
namespace Sword2 {
#define MAX_BLOCKS 999
#define Debug_Printf _vm->_debugger->DebugPrintf
MemoryManager::MemoryManager(Sword2Engine *vm) : _vm(vm) {
// The id stack contains all the possible ids for the memory blocks.
// We use this to ensure that no two blocks ever have the same id.
// The memory blocks are stored in an array, indexed on the block's
// id. This means that given a block id we can find the pointer with a
// simple array lookup.
// The memory block index is an array of pointers to the memory block
// array, sorted on the memory block's pointer. This means that given
// a pointer into a memory block we can find its id with binary
// searching.
//
// A balanced tree might have been more efficient - the index has to
// be re-sorted every time a block is allocated or freed - but such
// beasts are tricky to implement. Anyway, it wouldn't have made
// encoding or decoding pointers any faster, and these are by far the
// most common operations.
_idStack = (int16 *) malloc(MAX_BLOCKS * sizeof(int16));
_memBlocks = (MemBlock *) malloc(MAX_BLOCKS * sizeof(MemBlock));
_memBlockIndex = (MemBlock **) malloc(MAX_BLOCKS * sizeof(MemBlock *));
_totAlloc = 0;
_numBlocks = 0;
for (int i = 0; i < MAX_BLOCKS; i++) {
_idStack[i] = MAX_BLOCKS - i - 1;
_memBlocks[i].ptr = NULL;
_memBlockIndex[i] = NULL;
}
_idStackPtr = MAX_BLOCKS;
}
MemoryManager::~MemoryManager() {
for (int i = 0; i < MAX_BLOCKS; i++)
free(_memBlocks[i].ptr);
free(_memBlocks);
free(_memBlockIndex);
free(_idStack);
}
int32 MemoryManager::encodePtr(byte *ptr) {
if (ptr == NULL)
return 0;
int idx = findPointerInIndex(ptr);
assert(idx != -1);
uint32 id = _memBlockIndex[idx]->id;
uint32 offset = ptr - _memBlocks[id].ptr;
assert(id < 0x03ff);
assert(offset <= 0x003fffff);
assert(offset < _memBlocks[id].size);
return ((id + 1) << 22) | (ptr - _memBlocks[id].ptr);
}
byte *MemoryManager::decodePtr(int32 n) {
if (n == 0)
return NULL;
uint32 id = ((n & 0xffc00000) >> 22) - 1;
uint32 offset = n & 0x003fffff;
assert(_memBlocks[id].ptr);
assert(offset < _memBlocks[id].size);
return _memBlocks[id].ptr + offset;
}
int16 MemoryManager::findExactPointerInIndex(byte *ptr) {
int left = 0;
int right = _numBlocks - 1;
while (right >= left) {
int n = (left + right) / 2;
if (_memBlockIndex[n]->ptr == ptr)
return n;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
return -1;
}
int16 MemoryManager::findPointerInIndex(byte *ptr) {
int left = 0;
int right = _numBlocks - 1;
while (right >= left) {
int n = (left + right) / 2;
if (_memBlockIndex[n]->ptr <= ptr && _memBlockIndex[n]->ptr + _memBlockIndex[n]->size > ptr)
return n;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
return -1;
}
int16 MemoryManager::findInsertionPointInIndex(byte *ptr) {
if (_numBlocks == 0)
return 0;
int left = 0;
int right = _numBlocks - 1;
int n = 0;
while (right >= left) {
n = (left + right) / 2;
if (_memBlockIndex[n]->ptr == ptr)
return -1;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
if (_memBlockIndex[n]->ptr < ptr)
n++;
return n;
}
byte *MemoryManager::memAlloc(uint32 size, int16 uid) {
assert(_idStackPtr > 0);
// Get the new block's id from the stack.
int16 id = _idStack[--_idStackPtr];
// Allocate the new memory block
byte *ptr = (byte *) malloc(size);
assert(ptr);
_memBlocks[id].id = id;
_memBlocks[id].uid = uid;
_memBlocks[id].ptr = ptr;
_memBlocks[id].size = size;
// Update the memory block index.
int16 idx = findInsertionPointInIndex(ptr);
assert(idx != -1);
for (int i = _numBlocks; i > idx; i--)
_memBlockIndex[i] = _memBlockIndex[i - 1];
_memBlockIndex[idx] = &_memBlocks[id];
_numBlocks++;
_totAlloc += size;
return _memBlocks[id].ptr;
}
void MemoryManager::memFree(byte *ptr) {
int16 idx = findExactPointerInIndex(ptr);
if (idx == -1) {
warning("Freeing non-allocated pointer %p", ptr);
return;
}
// Put back the id on the stack
_idStack[_idStackPtr++] = _memBlockIndex[idx]->id;
// Release the memory block
free(_memBlockIndex[idx]->ptr);
_memBlockIndex[idx]->ptr = NULL;
_totAlloc -= _memBlockIndex[idx]->size;
// Remove the memory block from the index
_numBlocks--;
for (int i = idx; i < _numBlocks; i++)
_memBlockIndex[i] = _memBlockIndex[i + 1];
}
void MemoryManager::memDisplay() {
for (int i = 0; i < _numBlocks; i++)
Debug_Printf("%d: %ld bytes allocated by resource %d\n", i, _memBlocks[i].size, _memBlocks[i].uid);
}
void MemoryManager::memStatusStr(char *buf) {
if (_totAlloc < 1024)
sprintf(buf, "%u bytes in %d memory blocks", _totAlloc, _numBlocks);
else if (_totAlloc < 1024 * 1024)
sprintf(buf, "%uK in %d memory blocks", _totAlloc / 1024, _numBlocks);
else
sprintf(buf, "%.02fM in %d memory blocks", _totAlloc / 1048576., _numBlocks);
}
} // End of namespace Sword2