aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2/memory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword2/memory.cpp')
-rw-r--r--engines/sword2/memory.cpp244
1 files changed, 244 insertions, 0 deletions
diff --git a/engines/sword2/memory.cpp b/engines/sword2/memory.cpp
new file mode 100644
index 0000000000..7da4e86b51
--- /dev/null
+++ b/engines/sword2/memory.cpp
@@ -0,0 +1,244 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// 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 2 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/memory.h"
+
+namespace Sword2 {
+
+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_MEMORY_BLOCKS * sizeof(int16));
+ _memBlocks = (MemBlock *)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock));
+ _memBlockIndex = (MemBlock **)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock *));
+
+ _totAlloc = 0;
+ _numBlocks = 0;
+
+ for (int i = 0; i < MAX_MEMORY_BLOCKS; i++) {
+ _idStack[i] = MAX_MEMORY_BLOCKS - i - 1;
+ _memBlocks[i].ptr = NULL;
+ _memBlockIndex[i] = NULL;
+ }
+
+ _idStackPtr = MAX_MEMORY_BLOCKS;
+}
+
+MemoryManager::~MemoryManager() {
+ for (int i = 0; i < MAX_MEMORY_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];
+}
+
+} // End of namespace Sword2