mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-28 20:55:19 +00:00
bc2ec5afde
svn-id: r33235
595 lines
14 KiB
C++
595 lines
14 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
* This file contains the handle based Memory Manager code.
|
|
*/
|
|
|
|
#include "tinsel/heapmem.h"
|
|
#include "tinsel/timers.h" // For DwGetCurrentTime
|
|
|
|
namespace Tinsel {
|
|
|
|
// minimum memory required for MS-DOS version of game
|
|
#define MIN_MEM 2506752L
|
|
|
|
// list of all memory nodes
|
|
MEM_NODE mnodeList[NUM_MNODES];
|
|
|
|
// pointer to the linked list of free mnodes
|
|
static MEM_NODE *pFreeMemNodes;
|
|
|
|
#ifdef DEBUG
|
|
// diagnostic mnode counters
|
|
static int numNodes;
|
|
static int maxNodes;
|
|
#endif
|
|
|
|
// the mnode heap sentinel
|
|
static MEM_NODE heapSentinel;
|
|
|
|
//
|
|
static MEM_NODE *AllocMemNode(void);
|
|
|
|
|
|
/**
|
|
* Initialises the memory manager.
|
|
*/
|
|
void MemoryInit(void) {
|
|
MEM_NODE *pNode;
|
|
|
|
#ifdef DEBUG
|
|
// clear number of nodes in use
|
|
numNodes = 0;
|
|
#endif
|
|
|
|
// place first node on free list
|
|
pFreeMemNodes = mnodeList;
|
|
|
|
// link all other objects after first
|
|
for (int i = 1; i < NUM_MNODES; i++) {
|
|
mnodeList[i - 1].pNext = mnodeList + i;
|
|
}
|
|
|
|
// null the last mnode
|
|
mnodeList[NUM_MNODES - 1].pNext = NULL;
|
|
|
|
// allocatea big chunk of memory
|
|
const uint32 size = 2*MIN_MEM+655360L;
|
|
uint8 *mem = (uint8 *)malloc(size);
|
|
assert(mem);
|
|
|
|
// allocate a mnode for this memory
|
|
pNode = AllocMemNode();
|
|
|
|
// make sure mnode was allocated
|
|
assert(pNode);
|
|
|
|
// convert segment to memory address
|
|
pNode->pBaseAddr = mem;
|
|
|
|
// set size of the memory heap
|
|
pNode->size = size;
|
|
|
|
// clear the memory
|
|
memset(pNode->pBaseAddr, 0, size);
|
|
|
|
// set cyclic links to the sentinel
|
|
heapSentinel.pPrev = pNode;
|
|
heapSentinel.pNext = pNode;
|
|
pNode->pPrev = &heapSentinel;
|
|
pNode->pNext = &heapSentinel;
|
|
|
|
// flag sentinel as locked
|
|
heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/**
|
|
* Shows the maximum number of mnodes used at once.
|
|
*/
|
|
|
|
void MemoryStats(void) {
|
|
printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Allocate a mnode from the free list.
|
|
*/
|
|
static MEM_NODE *AllocMemNode(void) {
|
|
// get the first free mnode
|
|
MEM_NODE *pMemNode = pFreeMemNodes;
|
|
|
|
// make sure a mnode is available
|
|
assert(pMemNode); // Out of memory nodes
|
|
|
|
// the next free mnode
|
|
pFreeMemNodes = pMemNode->pNext;
|
|
|
|
// wipe out the mnode
|
|
memset(pMemNode, 0, sizeof(MEM_NODE));
|
|
|
|
#ifdef DEBUG
|
|
// one more mnode in use
|
|
if (++numNodes > maxNodes)
|
|
maxNodes = numNodes;
|
|
#endif
|
|
|
|
// 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 >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
#ifdef DEBUG
|
|
// one less mnode in use
|
|
--numNodes;
|
|
assert(numNodes >= 0);
|
|
#endif
|
|
|
|
// place free list in mnode next
|
|
pMemNode->pNext = pFreeMemNodes;
|
|
|
|
// add mnode to top of free list
|
|
pFreeMemNodes = pMemNode;
|
|
}
|
|
|
|
|
|
/**
|
|
* Tries to make space for the specified number of bytes on the specified heap.
|
|
* @param size Number of bytes to free up
|
|
* @param bDiscard When set - will discard blocks to fullfill the request
|
|
*/
|
|
bool HeapCompact(long size, bool bDiscard) {
|
|
MEM_NODE *pHeap = &heapSentinel;
|
|
MEM_NODE *pPrev, *pCur, *pOldest;
|
|
long largest; // size of largest free block
|
|
uint32 oldest; // time of the oldest discardable block
|
|
|
|
while (true) {
|
|
bool bChanged;
|
|
|
|
do {
|
|
bChanged = false;
|
|
for (pPrev = pHeap->pNext, pCur = pPrev->pNext;
|
|
pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) {
|
|
if (pPrev->flags == 0 && pCur->flags == 0) {
|
|
// set the changed flag
|
|
bChanged = true;
|
|
|
|
// both blocks are free - merge them
|
|
pPrev->size += pCur->size;
|
|
|
|
// unlink the current mnode
|
|
pPrev->pNext = pCur->pNext;
|
|
pCur->pNext->pPrev = pPrev;
|
|
|
|
// free the current mnode
|
|
FreeMemNode(pCur);
|
|
|
|
// leave the loop
|
|
break;
|
|
} else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE
|
|
&& pCur->flags == 0) {
|
|
// a free block after a moveable block - swap them
|
|
|
|
// set the changed flag
|
|
bChanged = true;
|
|
|
|
// move the unlocked blocks data up (can overlap)
|
|
memmove(pPrev->pBaseAddr + pCur->size,
|
|
pPrev->pBaseAddr, pPrev->size);
|
|
|
|
// swap the order in the linked list
|
|
pPrev->pPrev->pNext = pCur;
|
|
pCur->pNext->pPrev = pPrev;
|
|
|
|
pCur->pPrev = pPrev->pPrev;
|
|
pPrev->pPrev = pCur;
|
|
|
|
pPrev->pNext = pCur->pNext;
|
|
pCur->pNext = pPrev;
|
|
|
|
pCur->pBaseAddr = pPrev->pBaseAddr;
|
|
pPrev->pBaseAddr += pCur->size;
|
|
|
|
// leave the loop
|
|
break;
|
|
}
|
|
}
|
|
} while (bChanged);
|
|
|
|
// find the largest free block
|
|
for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
|
|
if (pCur->flags == 0 && pCur->size > largest)
|
|
largest = pCur->size;
|
|
}
|
|
|
|
if (largest >= size)
|
|
// we have freed enough memory
|
|
return true;
|
|
|
|
if (!bDiscard)
|
|
// we cannot free enough without discarding blocks
|
|
return false;
|
|
|
|
// find the oldest discardable block
|
|
oldest = DwGetCurrentTime();
|
|
pOldest = NULL;
|
|
for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
|
|
if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED))
|
|
== DWM_DISCARDABLE) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocates the specified number of bytes from the heap.
|
|
* @param flags Allocation attributes
|
|
* @param size Number of bytes to allocate
|
|
*/
|
|
MEM_NODE *MemoryAlloc(int flags, long size) {
|
|
MEM_NODE *pHeap = &heapSentinel;
|
|
MEM_NODE *pNode;
|
|
bool bCompacted = true; // set when heap has been compacted
|
|
|
|
// compact the heap if we are allocating fixed memory
|
|
if (flags & DWM_FIXED)
|
|
HeapCompact(MAX_INT, false);
|
|
|
|
while ((flags & DWM_NOALLOC) == 0 && bCompacted) {
|
|
// search the heap for a free block
|
|
|
|
for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) {
|
|
if (pNode->flags == 0 && pNode->size >= size) {
|
|
// a free block of the required size
|
|
pNode->flags = flags;
|
|
|
|
// update the LRU time
|
|
pNode->lruTime = DwGetCurrentTime() + 1;
|
|
|
|
if (pNode->size == size) {
|
|
// an exact fit
|
|
|
|
// check for zeroing the block
|
|
if (flags & DWM_ZEROINIT)
|
|
memset(pNode->pBaseAddr, 0, size);
|
|
|
|
if (flags & DWM_FIXED)
|
|
// lock the memory
|
|
return (MEM_NODE *)MemoryLock(pNode);
|
|
else
|
|
// just return the node
|
|
return pNode;
|
|
} else {
|
|
// allocate a node for the remainder of the free block
|
|
MEM_NODE *pTemp = AllocMemNode();
|
|
|
|
// calc size of the free block
|
|
long freeSize = pNode->size - size;
|
|
|
|
// set size of free block
|
|
pTemp->size = freeSize;
|
|
|
|
// set size of node
|
|
pNode->size = size;
|
|
|
|
if (flags & DWM_FIXED) {
|
|
// place the free node after pNode
|
|
pTemp->pBaseAddr = pNode->pBaseAddr + size;
|
|
pTemp->pNext = pNode->pNext;
|
|
pTemp->pPrev = pNode;
|
|
pNode->pNext->pPrev = pTemp;
|
|
pNode->pNext = pTemp;
|
|
|
|
// check for zeroing the block
|
|
if (flags & DWM_ZEROINIT)
|
|
memset(pNode->pBaseAddr, 0, size);
|
|
|
|
return (MEM_NODE *)MemoryLock(pNode);
|
|
} else {
|
|
// place the free node before pNode
|
|
pTemp->pBaseAddr = pNode->pBaseAddr;
|
|
pNode->pBaseAddr += freeSize;
|
|
pTemp->pNext = pNode;
|
|
pTemp->pPrev = pNode->pPrev;
|
|
pNode->pPrev->pNext = pTemp;
|
|
pNode->pPrev = pTemp;
|
|
|
|
// check for zeroing the block
|
|
if (flags & DWM_ZEROINIT)
|
|
memset(pNode->pBaseAddr, 0, size);
|
|
|
|
return pNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// compact the heap if we get to here
|
|
bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true);
|
|
}
|
|
|
|
// not allocated a block if we get to here
|
|
if (flags & DWM_DISCARDABLE) {
|
|
// chain a discarded node onto the end of the heap
|
|
pNode = AllocMemNode();
|
|
pNode->flags = flags | DWM_DISCARDED;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// could not allocate a block
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Discards the specified memory object.
|
|
* @param pMemNode Node of the memory object
|
|
*/
|
|
void MemoryDiscard(MEM_NODE *pMemNode) {
|
|
// validate mnode pointer
|
|
assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
// object must be discardable
|
|
assert(pMemNode->flags & DWM_DISCARDABLE);
|
|
|
|
// object cannot be locked
|
|
assert((pMemNode->flags & DWM_LOCKED) == 0);
|
|
|
|
if ((pMemNode->flags & DWM_DISCARDED) == 0) {
|
|
// allocate a free node to replace this node
|
|
MEM_NODE *pTemp = AllocMemNode();
|
|
|
|
// copy node data
|
|
memcpy(pTemp, pMemNode, sizeof(MEM_NODE));
|
|
|
|
// flag as a free block
|
|
pTemp->flags = 0;
|
|
|
|
// link in the free node
|
|
pTemp->pPrev->pNext = pTemp;
|
|
pTemp->pNext->pPrev = pTemp;
|
|
|
|
// discard the node
|
|
pMemNode->flags |= DWM_DISCARDED;
|
|
pMemNode->pBaseAddr = NULL;
|
|
pMemNode->size = 0;
|
|
|
|
// and place it at the end of the heap
|
|
while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL)
|
|
pTemp = pTemp->pNext;
|
|
|
|
// pTemp now points to the heap sentinel
|
|
// set mnode at the end of the list
|
|
pMemNode->pPrev = pTemp->pPrev;
|
|
pMemNode->pNext = pTemp;
|
|
|
|
// fix links to this mnode
|
|
pTemp->pPrev->pNext = pMemNode;
|
|
pTemp->pPrev = pMemNode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Frees the specified memory object and invalidates its node.
|
|
* @param pMemNode Node of the memory object
|
|
*/
|
|
void MemoryFree(MEM_NODE *pMemNode) {
|
|
MEM_NODE *pPrev, *pNext;
|
|
|
|
// validate mnode pointer
|
|
assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
// get pointer to the next mnode
|
|
pNext = pMemNode->pNext;
|
|
|
|
// get pointer to the previous mnode
|
|
pPrev = pMemNode->pPrev;
|
|
|
|
if (pPrev->flags == 0) {
|
|
// there is a previous free mnode
|
|
pPrev->size += pMemNode->size;
|
|
|
|
// unlink this mnode
|
|
pPrev->pNext = pNext; // previous to next
|
|
pNext->pPrev = pPrev; // next to previous
|
|
|
|
// free this mnode
|
|
FreeMemNode(pMemNode);
|
|
|
|
pMemNode = pPrev;
|
|
}
|
|
if (pNext->flags == 0) {
|
|
// the next mnode is free
|
|
pMemNode->size += pNext->size;
|
|
|
|
// flag as a free block
|
|
pMemNode->flags = 0;
|
|
|
|
// unlink the next mnode
|
|
pMemNode->pNext = pNext->pNext;
|
|
pNext->pNext->pPrev = pMemNode;
|
|
|
|
// free the next mnode
|
|
FreeMemNode(pNext);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
// validate mnode pointer
|
|
assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
// 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;
|
|
|
|
// return memory objects base address
|
|
return pMemNode->pBaseAddr;
|
|
}
|
|
|
|
/**
|
|
* Changes the size or attributes of a specified memory object.
|
|
* @param pMemNode Node of the memory object
|
|
* @param size New size of block
|
|
* @param flags How to reallocate the object
|
|
*/
|
|
MEM_NODE *MemoryReAlloc(MEM_NODE *pMemNode, long size, int flags) {
|
|
MEM_NODE *pNew;
|
|
|
|
// validate mnode pointer
|
|
assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
// validate the flags
|
|
// cannot be fixed and moveable
|
|
assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE));
|
|
|
|
// cannot be fixed and discardable
|
|
assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE));
|
|
|
|
// must be fixed or moveable
|
|
assert(flags & (DWM_FIXED | DWM_MOVEABLE));
|
|
|
|
// align the size to machine boundary requirements
|
|
size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1);
|
|
|
|
// validate the size
|
|
assert(size);
|
|
|
|
// make sure we want the node on the same heap
|
|
assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC)));
|
|
|
|
if (size == pMemNode->size) {
|
|
// must be just a change in flags
|
|
|
|
// update the nodes flags
|
|
pMemNode->flags = flags;
|
|
} else {
|
|
// unlink the mnode from the current heap
|
|
pMemNode->pNext->pPrev = pMemNode->pPrev;
|
|
pMemNode->pPrev->pNext = pMemNode->pNext;
|
|
|
|
// allocate a new node
|
|
pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size);
|
|
|
|
// make sure memory allocated
|
|
assert(pNew != NULL);
|
|
|
|
// update the nodes flags
|
|
pNew->flags = flags;
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (flags & DWM_FIXED)
|
|
// lock the memory
|
|
return (MEM_NODE *)MemoryLock(pMemNode);
|
|
else
|
|
// just return the node
|
|
return pMemNode;
|
|
}
|
|
|
|
/**
|
|
* Unlocks a memory object.
|
|
* @param pMemNode Node of the memory object
|
|
*/
|
|
void MemoryUnlock(MEM_NODE *pMemNode) {
|
|
// validate mnode pointer
|
|
assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
|
|
|
|
// make sure memory object is already locked
|
|
assert(pMemNode->flags & DWM_LOCKED);
|
|
|
|
// clear the lock flag
|
|
pMemNode->flags &= ~DWM_LOCKED;
|
|
|
|
// update the LRU time
|
|
pMemNode->lruTime = DwGetCurrentTime();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the mnode associated with the specified pointer to a memory object.
|
|
* @param pMem Address of memory object
|
|
*/
|
|
MEM_NODE *MemoryHandle(void *pMem) {
|
|
MEM_NODE *pNode;
|
|
// search the DOS heap
|
|
for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) {
|
|
if (pNode->pBaseAddr == pMem)
|
|
// found it
|
|
return pNode;
|
|
}
|
|
|
|
// not found if we get to here
|
|
return NULL;
|
|
}
|
|
|
|
} // end of namespace Tinsel
|