/* 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