Initial commit
This commit is contained in:
478
engines/tinsel/heapmem.cpp
Normal file
478
engines/tinsel/heapmem.cpp
Normal file
@@ -0,0 +1,478 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 ((TinselVersion == 3) ? 0x0200 : 0x0004) ///< the objects memory block is locked
|
||||
#define DWM_SENTINEL 0x0008 ///< the objects memory block is a sentinel
|
||||
// Noir
|
||||
#define DWM_V3X20 0x0020 ///< unknown
|
||||
#define DWM_V3X4 0x0004 ///< unknown
|
||||
|
||||
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[4] = {5 * 1024 * 1024, 5 * 1024 * 1024, 10 * 1024 * 1024, 512 * 1024 * 1024};
|
||||
|
||||
// These vars are reset upon engine destruction
|
||||
|
||||
// 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 = nullptr;
|
||||
|
||||
// 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 == 1) size = MemoryPoolSize[1];
|
||||
else if (TinselVersion == 2) size = MemoryPoolSize[2];
|
||||
else if (TinselVersion == 3) {
|
||||
warning("TODO: Find the correct memory pool size for Noir, using 512 MiB for now");
|
||||
size = MemoryPoolSize[3];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
memset(g_mnodeList, 0, sizeof(g_mnodeList));
|
||||
memset(g_s_fixedMnodesList, 0, sizeof(g_s_fixedMnodesList));
|
||||
g_pFreeMemNodes = nullptr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 = nullptr;
|
||||
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 = nullptr;
|
||||
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
|
||||
Reference in New Issue
Block a user