scummvm/engines/tinsel/heapmem.cpp
2014-02-18 02:39:39 +01:00

471 lines
12 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 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.
*
* This file contains the handle based Memory Manager code.
*/
#include "tinsel/heapmem.h"
#include "tinsel/timers.h" // For DwGetCurrentTime
#include "tinsel/tinsel.h"
namespace Tinsel {
#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192)
// internal allocation flags
#define DWM_USED 0x0001 ///< the objects memory block is in use
#define DWM_DISCARDED 0x0002 ///< the objects memory block has been discarded
#define DWM_LOCKED 0x0004 ///< the objects memory block is locked
#define DWM_SENTINEL 0x0008 ///< the objects memory block is a sentinel
struct MEM_NODE {
MEM_NODE *pNext; // link to the next node in the list
MEM_NODE *pPrev; // link to the previous node in the list
uint8 *pBaseAddr; // base address of the memory object
long size; // size of the memory object
uint32 lruTime; // time when memory object was last accessed
int flags; // allocation attributes
};
// Specifies the total amount of memory required for DW1 demo, DW1, or DW2 respectively.
// Currently this is set at 5MB for the DW1 demo and DW1 and 10MB for DW2
// This could probably be reduced somewhat
// If the memory is not enough, the engine throws an "Out of memory" error in handle.cpp inside LockMem()
static const uint32 MemoryPoolSize[3] = {5 * 1024 * 1024, 5 * 1024 * 1024, 10 * 1024 * 1024};
// FIXME: Avoid non-const global vars
// list of all memory nodes
MEM_NODE g_mnodeList[NUM_MNODES];
// pointer to the linked list of free mnodes
static MEM_NODE *g_pFreeMemNodes;
// list of all fixed memory nodes
MEM_NODE g_s_fixedMnodesList[5];
// the mnode heap sentinel
static MEM_NODE g_heapSentinel;
//
static MEM_NODE *AllocMemNode();
#ifdef DEBUG
static void MemoryStats() {
int usedNodes = 0;
int allocedNodes = 0;
int lockedNodes = 0;
int lockedSize = 0;
int totalSize = 0;
const MEM_NODE *pHeap = &g_heapSentinel;
MEM_NODE *pCur;
for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
usedNodes++;
totalSize += pCur->size;
if (pCur->flags)
allocedNodes++;
if (pCur->flags & DWM_LOCKED) {
lockedNodes++;
lockedSize += pCur->size;
}
}
debug("%d nodes used, %d alloced, %d locked; %d bytes locked, %d used",
usedNodes, allocedNodes, lockedNodes, lockedSize, totalSize);
}
#endif
/**
* Initializes the memory manager.
*/
void MemoryInit() {
// place first node on free list
g_pFreeMemNodes = g_mnodeList;
// link all other objects after first
memset(g_mnodeList, 0, sizeof(g_mnodeList));
for (int i = 1; i < NUM_MNODES; i++) {
g_mnodeList[i - 1].pNext = g_mnodeList + i;
}
// null the last mnode
g_mnodeList[NUM_MNODES - 1].pNext = NULL;
// clear list of fixed memory nodes
memset(g_s_fixedMnodesList, 0, sizeof(g_s_fixedMnodesList));
// set cyclic links to the sentinel
g_heapSentinel.pPrev = &g_heapSentinel;
g_heapSentinel.pNext = &g_heapSentinel;
// flag sentinel as locked
g_heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL;
// store the current heap size in the sentinel
uint32 size = MemoryPoolSize[0];
if (TinselVersion == TINSEL_V1) size = MemoryPoolSize[1];
else if (TinselVersion == TINSEL_V2) size = MemoryPoolSize[2];
g_heapSentinel.size = size;
}
/**
* Deinitializes the memory manager.
*/
void MemoryDeinit() {
const MEM_NODE *pHeap = &g_heapSentinel;
MEM_NODE *pCur;
pCur = g_s_fixedMnodesList;
for (int i = 0; i < ARRAYSIZE(g_s_fixedMnodesList); ++i, ++pCur) {
free(pCur->pBaseAddr);
pCur->pBaseAddr = 0;
}
for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
free(pCur->pBaseAddr);
pCur->pBaseAddr = 0;
}
}
/**
* Allocate a mnode from the free list.
*/
static MEM_NODE *AllocMemNode() {
// get the first free mnode
MEM_NODE *pMemNode = g_pFreeMemNodes;
// make sure a mnode is available
assert(pMemNode); // Out of memory nodes
// the next free mnode
g_pFreeMemNodes = pMemNode->pNext;
// wipe out the mnode
memset(pMemNode, 0, sizeof(MEM_NODE));
// return new mnode
return pMemNode;
}
/**
* Return a mnode back to the free list.
* @param pMemNode Node of the memory object
*/
void FreeMemNode(MEM_NODE *pMemNode) {
// validate mnode pointer
assert(pMemNode >= g_mnodeList && pMemNode <= g_mnodeList + NUM_MNODES - 1);
// place free list in mnode next
pMemNode->pNext = g_pFreeMemNodes;
// add mnode to top of free list
g_pFreeMemNodes = pMemNode;
}
/**
* Tries to make space for the specified number of bytes on the specified heap.
* @param size Number of bytes to free up
* @return true if any blocks were discarded, false otherwise
*/
static bool HeapCompact(long size) {
const MEM_NODE *pHeap = &g_heapSentinel;
MEM_NODE *pCur, *pOldest;
uint32 oldest; // time of the oldest discardable block
while (g_heapSentinel.size < size) {
// find the oldest discardable block
oldest = DwGetCurrentTime();
pOldest = NULL;
for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
if (pCur->flags == DWM_USED) {
// found a non-discarded discardable block
if (pCur->lruTime < oldest) {
oldest = pCur->lruTime;
pOldest = pCur;
}
}
}
if (pOldest)
// discard the oldest block
MemoryDiscard(pOldest);
else
// cannot discard any blocks
return false;
}
// we have freed enough memory
return true;
}
/**
* Allocates the specified number of bytes from the heap.
* @param flags Allocation attributes
* @param size Number of bytes to allocate
*/
static MEM_NODE *MemoryAlloc(long size) {
MEM_NODE *pHeap = &g_heapSentinel;
#ifdef SCUMM_NEED_ALIGNMENT
const int alignPadding = sizeof(void *) - 1;
size = (size + alignPadding) & ~alignPadding; //round up to nearest multiple of sizeof(void *), this ensures the addresses that are returned are alignment-safe.
#endif
// compact the heap to make up room for 'size' bytes, if necessary
if (!HeapCompact(size))
return 0;
// success! we may allocate a new node of the right size
// Allocate a node.
MEM_NODE *pNode = AllocMemNode();
// Allocate memory for the node.
pNode->pBaseAddr = (byte *)malloc(size);
// Verify that we got the memory.
// TODO: If this fails, we should first try to compact the heap some further.
assert(pNode->pBaseAddr);
// Subtract size of new block from total
g_heapSentinel.size -= size;
#ifdef DEBUG
MemoryStats();
#endif
// Set flags, LRU time and size
pNode->flags = DWM_USED;
pNode->lruTime = DwGetCurrentTime() + 1;
pNode->size = size;
// set mnode at the end of the list
pNode->pPrev = pHeap->pPrev;
pNode->pNext = pHeap;
// fix links to this mnode
pHeap->pPrev->pNext = pNode;
pHeap->pPrev = pNode;
return pNode;
}
/**
* Allocate a discarded MEM_NODE. Actual memory can be assigned to it
* by using MemoryReAlloc().
*/
MEM_NODE *MemoryNoAlloc() {
MEM_NODE *pHeap = &g_heapSentinel;
// chain a discarded node onto the end of the heap
MEM_NODE *pNode = AllocMemNode();
pNode->flags = DWM_USED | DWM_DISCARDED;
pNode->lruTime = DwGetCurrentTime();
pNode->size = 0;
// set mnode at the end of the list
pNode->pPrev = pHeap->pPrev;
pNode->pNext = pHeap;
// fix links to this mnode
pHeap->pPrev->pNext = pNode;
pHeap->pPrev = pNode;
// return the discarded node
return pNode;
}
/**
* Allocate a fixed block of data.
* @todo We really should keep track of the allocated pointers,
* so that we can discard them later on, when the engine quits.
*/
MEM_NODE *MemoryAllocFixed(long size) {
#ifdef SCUMM_NEED_ALIGNMENT
const int alignPadding = sizeof(void *) - 1;
size = (size + alignPadding) & ~alignPadding; //round up to nearest multiple of sizeof(void *), this ensures the addresses that are returned are alignment-safe.
#endif
// Search for a free entry in s_fixedMnodesList
MEM_NODE *pNode = g_s_fixedMnodesList;
for (int i = 0; i < ARRAYSIZE(g_s_fixedMnodesList); ++i, ++pNode) {
if (!pNode->pBaseAddr) {
pNode->pNext = 0;
pNode->pPrev = 0;
pNode->pBaseAddr = (byte *)malloc(size);
pNode->size = size;
pNode->lruTime = DwGetCurrentTime() + 1;
pNode->flags = DWM_USED;
// Subtract size of new block from total
g_heapSentinel.size -= size;
return pNode;
}
}
return 0;
}
/**
* Discards the specified memory object.
* @param pMemNode Node of the memory object
*/
void MemoryDiscard(MEM_NODE *pMemNode) {
// validate mnode pointer
assert(pMemNode >= g_mnodeList && pMemNode <= g_mnodeList + NUM_MNODES - 1);
// object must be in use and locked
assert((pMemNode->flags & (DWM_USED | DWM_LOCKED)) == DWM_USED);
// discard it if it isn't already
if ((pMemNode->flags & DWM_DISCARDED) == 0) {
// free memory
free(pMemNode->pBaseAddr);
g_heapSentinel.size += pMemNode->size;
#ifdef DEBUG
MemoryStats();
#endif
// mark the node as discarded
pMemNode->flags |= DWM_DISCARDED;
pMemNode->pBaseAddr = NULL;
pMemNode->size = 0;
}
}
/**
* Locks a memory object and returns a pointer to the first byte
* of the objects memory block.
* @param pMemNode Node of the memory object
*/
void *MemoryLock(MEM_NODE *pMemNode) {
// make sure memory object is not already locked
assert((pMemNode->flags & DWM_LOCKED) == 0);
// check for a discarded or null memory object
if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0)
return NULL;
// set the lock flag
pMemNode->flags |= DWM_LOCKED;
#ifdef DEBUG
MemoryStats();
#endif
// return memory objects base address
return pMemNode->pBaseAddr;
}
/**
* Unlocks a memory object.
* @param pMemNode Node of the memory object
*/
void MemoryUnlock(MEM_NODE *pMemNode) {
// make sure memory object is already locked
assert(pMemNode->flags & DWM_LOCKED);
// clear the lock flag
pMemNode->flags &= ~DWM_LOCKED;
#ifdef DEBUG
MemoryStats();
#endif
// update the LRU time
pMemNode->lruTime = DwGetCurrentTime();
}
/**
* Changes the size of a specified memory object and re-allocate it if necessary.
* @param pMemNode Node of the memory object
* @param size New size of block
*/
void MemoryReAlloc(MEM_NODE *pMemNode, long size) {
MEM_NODE *pNew;
// validate mnode pointer
assert(pMemNode >= g_mnodeList && pMemNode <= g_mnodeList + NUM_MNODES - 1);
// align the size to machine boundary requirements
size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
// validate the size
assert(size);
if (size != pMemNode->size) {
// make sure memory object is discarded and not locked
assert(pMemNode->flags == (DWM_USED | DWM_DISCARDED));
assert(pMemNode->size == 0);
// unlink the mnode from the current heap
pMemNode->pNext->pPrev = pMemNode->pPrev;
pMemNode->pPrev->pNext = pMemNode->pNext;
// allocate a new node
pNew = MemoryAlloc(size);
// make sure memory allocated
assert(pNew != NULL);
// copy the node to the current node
memcpy(pMemNode, pNew, sizeof(MEM_NODE));
// relink the mnode into the list
pMemNode->pPrev->pNext = pMemNode;
pMemNode->pNext->pPrev = pMemNode;
// free the new node
FreeMemNode(pNew);
}
assert(pMemNode->pBaseAddr);
}
/**
* Touch a memory object by updating its LRU time.
* @param pMemNode Node of the memory object
*/
void MemoryTouch(MEM_NODE *pMemNode) {
// update the LRU time
pMemNode->lruTime = DwGetCurrentTime();
}
uint8 *MemoryDeref(MEM_NODE *pMemNode) {
return pMemNode->pBaseAddr;
}
} // End of namespace Tinsel