mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 18:02:05 +00:00
471 lines
12 KiB
C++
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
|