2008-07-23 09:02:47 +00:00
/* 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 .
2014-02-18 02:34:25 +01:00
*
2008-07-23 09:02:47 +00:00
* 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 .
2014-02-18 02:34:25 +01:00
*
2008-07-23 09:02:47 +00:00
* 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
2008-12-01 20:35:36 +00:00
# include "tinsel/tinsel.h"
2008-07-23 09:02:47 +00:00
namespace Tinsel {
2009-10-26 16:01:34 +00:00
2009-10-26 20:36:44 +00:00
# define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192)
2009-10-26 16:01:34 +00:00
// internal allocation flags
2009-10-26 20:35:06 +00:00
# define DWM_USED 0x0001 ///< the objects memory block is in use
2009-10-27 00:37:23 +00:00
# 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
2009-10-26 16:01:34 +00:00
2009-10-26 20:38:34 +00:00
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
} ;
2008-12-01 20:35:36 +00:00
// Specifies the total amount of memory required for DW1 demo, DW1, or DW2 respectively.
2008-12-21 12:30:30 +00:00
// 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()
2009-10-26 10:40:16 +00:00
static const uint32 MemoryPoolSize [ 3 ] = { 5 * 1024 * 1024 , 5 * 1024 * 1024 , 10 * 1024 * 1024 } ;
2008-07-23 09:02:47 +00:00
2010-11-16 09:53:55 +00:00
// FIXME: Avoid non-const global vars
2008-07-23 09:02:47 +00:00
// list of all memory nodes
2012-02-22 23:14:29 +01:00
MEM_NODE g_mnodeList [ NUM_MNODES ] ;
2008-07-23 09:02:47 +00:00
// pointer to the linked list of free mnodes
2012-02-22 23:14:29 +01:00
static MEM_NODE * g_pFreeMemNodes ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:36:56 +00:00
// list of all fixed memory nodes
2012-02-22 23:14:29 +01:00
MEM_NODE g_s_fixedMnodesList [ 5 ] ;
2009-10-27 00:36:56 +00:00
2008-07-23 09:02:47 +00:00
// the mnode heap sentinel
2012-02-22 23:14:29 +01:00
static MEM_NODE g_heapSentinel ;
2008-07-23 09:02:47 +00:00
//
2009-11-02 21:54:57 +00:00
static MEM_NODE * AllocMemNode ( ) ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:54 +00:00
# ifdef DEBUG
static void MemoryStats ( ) {
int usedNodes = 0 ;
int allocedNodes = 0 ;
int lockedNodes = 0 ;
int lockedSize = 0 ;
int totalSize = 0 ;
2012-02-22 23:14:29 +01:00
const MEM_NODE * pHeap = & g_heapSentinel ;
2009-10-27 00:37:54 +00:00
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 ;
}
}
2010-11-01 16:04:47 +00:00
debug ( " %d nodes used, %d alloced, %d locked; %d bytes locked, %d used " ,
2009-10-27 00:37:54 +00:00
usedNodes , allocedNodes , lockedNodes , lockedSize , totalSize ) ;
}
# endif
2008-07-23 09:02:47 +00:00
/**
2011-05-25 11:17:11 -04:00
* Initializes the memory manager .
2008-07-23 09:02:47 +00:00
*/
2009-10-27 00:36:56 +00:00
void MemoryInit ( ) {
2008-07-23 09:02:47 +00:00
// place first node on free list
2012-02-22 23:14:29 +01:00
g_pFreeMemNodes = g_mnodeList ;
2008-07-23 09:02:47 +00:00
// link all other objects after first
2012-02-22 23:14:29 +01:00
memset ( g_mnodeList , 0 , sizeof ( g_mnodeList ) ) ;
2008-07-23 09:02:47 +00:00
for ( int i = 1 ; i < NUM_MNODES ; i + + ) {
2012-02-22 23:14:29 +01:00
g_mnodeList [ i - 1 ] . pNext = g_mnodeList + i ;
2008-07-23 09:02:47 +00:00
}
// null the last mnode
2012-02-22 23:14:29 +01:00
g_mnodeList [ NUM_MNODES - 1 ] . pNext = NULL ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:36:56 +00:00
// clear list of fixed memory nodes
2012-02-22 23:14:29 +01:00
memset ( g_s_fixedMnodesList , 0 , sizeof ( g_s_fixedMnodesList ) ) ;
2009-10-27 00:36:56 +00:00
2008-07-23 09:02:47 +00:00
// set cyclic links to the sentinel
2012-02-22 23:14:29 +01:00
g_heapSentinel . pPrev = & g_heapSentinel ;
g_heapSentinel . pNext = & g_heapSentinel ;
2008-07-23 09:02:47 +00:00
// flag sentinel as locked
2012-02-22 23:14:29 +01:00
g_heapSentinel . flags = DWM_LOCKED | DWM_SENTINEL ;
2009-10-27 00:36:56 +00:00
2009-10-27 00:37:23 +00:00
// 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 ] ;
2012-02-22 23:14:29 +01:00
g_heapSentinel . size = size ;
2008-07-23 09:02:47 +00:00
}
2009-10-27 00:36:56 +00:00
/**
2011-05-25 11:17:11 -04:00
* Deinitializes the memory manager .
2009-10-27 00:36:56 +00:00
*/
void MemoryDeinit ( ) {
2012-02-22 23:14:29 +01:00
const MEM_NODE * pHeap = & g_heapSentinel ;
2009-10-27 00:37:23 +00:00
MEM_NODE * pCur ;
2012-02-22 23:14:29 +01:00
pCur = g_s_fixedMnodesList ;
for ( int i = 0 ; i < ARRAYSIZE ( g_s_fixedMnodesList ) ; + + i , + + pCur ) {
2009-10-27 00:37:23 +00:00
free ( pCur - > pBaseAddr ) ;
pCur - > pBaseAddr = 0 ;
2009-10-27 00:36:56 +00:00
}
2009-10-27 00:37:23 +00:00
for ( pCur = pHeap - > pNext ; pCur ! = pHeap ; pCur = pCur - > pNext ) {
free ( pCur - > pBaseAddr ) ;
pCur - > pBaseAddr = 0 ;
}
2009-10-27 00:36:56 +00:00
}
2008-07-23 09:02:47 +00:00
/**
* Allocate a mnode from the free list .
*/
2009-11-02 21:54:57 +00:00
static MEM_NODE * AllocMemNode ( ) {
2008-07-23 09:02:47 +00:00
// get the first free mnode
2012-02-22 23:14:29 +01:00
MEM_NODE * pMemNode = g_pFreeMemNodes ;
2008-07-23 09:02:47 +00:00
// make sure a mnode is available
assert ( pMemNode ) ; // Out of memory nodes
// the next free mnode
2012-02-22 23:14:29 +01:00
g_pFreeMemNodes = pMemNode - > pNext ;
2008-07-23 09:02:47 +00:00
// 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
*/
2008-07-23 10:27:24 +00:00
void FreeMemNode ( MEM_NODE * pMemNode ) {
2008-07-23 09:02:47 +00:00
// validate mnode pointer
2012-02-22 23:14:29 +01:00
assert ( pMemNode > = g_mnodeList & & pMemNode < = g_mnodeList + NUM_MNODES - 1 ) ;
2008-07-23 09:02:47 +00:00
// place free list in mnode next
2012-02-22 23:14:29 +01:00
pMemNode - > pNext = g_pFreeMemNodes ;
2008-07-23 09:02:47 +00:00
// add mnode to top of free list
2012-02-22 23:14:29 +01:00
g_pFreeMemNodes = pMemNode ;
2008-07-23 09:02:47 +00:00
}
/**
* Tries to make space for the specified number of bytes on the specified heap .
* @ param size Number of bytes to free up
2009-10-26 16:01:34 +00:00
* @ return true if any blocks were discarded , false otherwise
2008-07-23 09:02:47 +00:00
*/
2009-10-27 00:37:54 +00:00
static bool HeapCompact ( long size ) {
2012-02-22 23:14:29 +01:00
const MEM_NODE * pHeap = & g_heapSentinel ;
2009-10-27 00:37:23 +00:00
MEM_NODE * pCur , * pOldest ;
2008-07-23 09:02:47 +00:00
uint32 oldest ; // time of the oldest discardable block
2012-02-22 23:14:29 +01:00
while ( g_heapSentinel . size < size ) {
2008-07-23 09:02:47 +00:00
// find the oldest discardable block
oldest = DwGetCurrentTime ( ) ;
pOldest = NULL ;
for ( pCur = pHeap - > pNext ; pCur ! = pHeap ; pCur = pCur - > pNext ) {
2009-10-26 20:35:06 +00:00
if ( pCur - > flags = = DWM_USED ) {
2008-07-23 09:02:47 +00:00
// found a non-discarded discardable block
if ( pCur - > lruTime < oldest ) {
oldest = pCur - > lruTime ;
pOldest = pCur ;
}
}
}
2009-01-01 15:06:43 +00:00
2008-07-23 09:02:47 +00:00
if ( pOldest )
// discard the oldest block
MemoryDiscard ( pOldest ) ;
else
// cannot discard any blocks
return false ;
}
2009-10-27 00:37:23 +00:00
// we have freed enough memory
return true ;
2008-07-23 09:02:47 +00:00
}
/**
* Allocates the specified number of bytes from the heap .
* @ param flags Allocation attributes
* @ param size Number of bytes to allocate
*/
2009-10-26 20:35:06 +00:00
static MEM_NODE * MemoryAlloc ( long size ) {
2012-02-22 23:14:29 +01:00
MEM_NODE * pHeap = & g_heapSentinel ;
2008-07-23 09:02:47 +00:00
2008-12-24 16:10:55 +00:00
# ifdef SCUMM_NEED_ALIGNMENT
2012-02-15 09:53:31 -06:00
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.
2008-12-24 16:10:55 +00:00
# endif
2009-10-27 00:37:23 +00:00
// compact the heap to make up room for 'size' bytes, if necessary
2009-10-27 00:37:54 +00:00
if ( ! HeapCompact ( size ) )
2009-10-27 00:37:23 +00:00
return 0 ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// success! we may allocate a new node of the right size
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// Allocate a node.
MEM_NODE * pNode = AllocMemNode ( ) ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// Allocate memory for the node.
pNode - > pBaseAddr = ( byte * ) malloc ( size ) ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// Verify that we got the memory.
// TODO: If this fails, we should first try to compact the heap some further.
assert ( pNode - > pBaseAddr ) ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// Subtract size of new block from total
2012-02-22 23:14:29 +01:00
g_heapSentinel . size - = size ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:54 +00:00
# ifdef DEBUG
MemoryStats ( ) ;
# endif
2009-10-27 00:37:23 +00:00
// Set flags, LRU time and size
pNode - > flags = DWM_USED ;
2009-10-27 00:37:54 +00:00
pNode - > lruTime = DwGetCurrentTime ( ) + 1 ;
2009-10-27 00:37:23 +00:00
pNode - > size = size ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
// set mnode at the end of the list
pNode - > pPrev = pHeap - > pPrev ;
pNode - > pNext = pHeap ;
2009-10-26 10:41:11 +00:00
2009-10-27 00:37:23 +00:00
// fix links to this mnode
pHeap - > pPrev - > pNext = pNode ;
pHeap - > pPrev = pNode ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:23 +00:00
return pNode ;
2009-10-26 16:01:34 +00:00
}
2008-07-23 09:02:47 +00:00
2009-10-26 16:01:34 +00:00
/**
* Allocate a discarded MEM_NODE . Actual memory can be assigned to it
* by using MemoryReAlloc ( ) .
*/
MEM_NODE * MemoryNoAlloc ( ) {
2012-02-22 23:14:29 +01:00
MEM_NODE * pHeap = & g_heapSentinel ;
2008-07-23 09:02:47 +00:00
2009-10-26 16:01:34 +00:00
// chain a discarded node onto the end of the heap
MEM_NODE * pNode = AllocMemNode ( ) ;
2009-10-26 20:35:06 +00:00
pNode - > flags = DWM_USED | DWM_DISCARDED ;
2009-10-27 00:37:23 +00:00
pNode - > lruTime = DwGetCurrentTime ( ) ;
pNode - > size = 0 ;
2008-07-23 09:02:47 +00:00
2009-10-26 16:01:34 +00:00
// set mnode at the end of the list
pNode - > pPrev = pHeap - > pPrev ;
pNode - > pNext = pHeap ;
2008-07-23 09:02:47 +00:00
2009-10-26 16:01:34 +00:00
// fix links to this mnode
pHeap - > pPrev - > pNext = pNode ;
pHeap - > pPrev = pNode ;
2008-07-23 09:02:47 +00:00
2009-10-26 16:01:34 +00:00
// return the discarded node
return pNode ;
}
2009-10-26 10:41:11 +00:00
2009-10-26 16:01:34 +00:00
/**
* Allocate a fixed block of data .
2009-10-27 00:36:56 +00:00
* @ todo We really should keep track of the allocated pointers ,
2009-10-26 16:01:34 +00:00
* so that we can discard them later on , when the engine quits .
*/
2009-10-27 00:36:56 +00:00
MEM_NODE * MemoryAllocFixed ( long size ) {
2009-10-26 10:41:11 +00:00
# ifdef SCUMM_NEED_ALIGNMENT
2012-02-15 09:53:31 -06:00
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.
2009-10-26 10:41:11 +00:00
# endif
2009-10-27 00:36:56 +00:00
// Search for a free entry in s_fixedMnodesList
2012-02-22 23:14:29 +01:00
MEM_NODE * pNode = g_s_fixedMnodesList ;
for ( int i = 0 ; i < ARRAYSIZE ( g_s_fixedMnodesList ) ; + + i , + + pNode ) {
2009-10-27 00:36:56 +00:00
if ( ! pNode - > pBaseAddr ) {
pNode - > pNext = 0 ;
pNode - > pPrev = 0 ;
pNode - > pBaseAddr = ( byte * ) malloc ( size ) ;
pNode - > size = size ;
2009-10-27 00:37:54 +00:00
pNode - > lruTime = DwGetCurrentTime ( ) + 1 ;
2009-10-27 00:36:56 +00:00
pNode - > flags = DWM_USED ;
2009-10-27 00:37:23 +00:00
// Subtract size of new block from total
2012-02-22 23:14:29 +01:00
g_heapSentinel . size - = size ;
2009-10-27 00:37:23 +00:00
2009-10-27 00:36:56 +00:00
return pNode ;
}
}
return 0 ;
2009-10-26 10:41:11 +00:00
}
2008-07-23 09:02:47 +00:00
/**
* Discards the specified memory object .
* @ param pMemNode Node of the memory object
*/
2008-07-23 10:27:24 +00:00
void MemoryDiscard ( MEM_NODE * pMemNode ) {
2008-07-23 09:02:47 +00:00
// validate mnode pointer
2012-02-22 23:14:29 +01:00
assert ( pMemNode > = g_mnodeList & & pMemNode < = g_mnodeList + NUM_MNODES - 1 ) ;
2008-07-23 09:02:47 +00:00
2009-10-26 20:35:06 +00:00
// object must be in use and locked
assert ( ( pMemNode - > flags & ( DWM_USED | DWM_LOCKED ) ) = = DWM_USED ) ;
2008-07-23 09:02:47 +00:00
2009-10-26 20:35:06 +00:00
// discard it if it isn't already
2008-07-23 09:02:47 +00:00
if ( ( pMemNode - > flags & DWM_DISCARDED ) = = 0 ) {
2009-10-27 00:37:23 +00:00
// free memory
free ( pMemNode - > pBaseAddr ) ;
2012-02-22 23:14:29 +01:00
g_heapSentinel . size + = pMemNode - > size ;
2008-07-23 09:02:47 +00:00
2009-10-27 00:37:54 +00:00
# ifdef DEBUG
MemoryStats ( ) ;
# endif
2009-10-27 00:37:23 +00:00
// mark the node as discarded
2008-07-23 09:02:47 +00:00
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
*/
2008-07-23 10:27:24 +00:00
void * MemoryLock ( MEM_NODE * pMemNode ) {
2008-07-23 09:02:47 +00:00
// 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 ;
2009-10-27 00:37:54 +00:00
# ifdef DEBUG
MemoryStats ( ) ;
# endif
2008-07-23 09:02:47 +00:00
// return memory objects base address
return pMemNode - > pBaseAddr ;
}
2009-10-27 00:37:54 +00:00
/**
* 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 ( ) ;
}
2008-07-23 09:02:47 +00:00
/**
2009-10-26 16:01:34 +00:00
* Changes the size of a specified memory object and re - allocate it if necessary .
2008-07-23 09:02:47 +00:00
* @ param pMemNode Node of the memory object
* @ param size New size of block
*/
2009-10-26 16:01:34 +00:00
void MemoryReAlloc ( MEM_NODE * pMemNode , long size ) {
2008-07-23 10:27:24 +00:00
MEM_NODE * pNew ;
2008-07-23 09:02:47 +00:00
// validate mnode pointer
2012-02-22 23:14:29 +01:00
assert ( pMemNode > = g_mnodeList & & pMemNode < = g_mnodeList + NUM_MNODES - 1 ) ;
2008-07-23 09:02:47 +00:00
// align the size to machine boundary requirements
2009-08-02 21:44:21 +00:00
size = ( size + sizeof ( void * ) - 1 ) & ~ ( sizeof ( void * ) - 1 ) ;
2008-07-23 09:02:47 +00:00
// validate the size
assert ( size ) ;
2009-10-26 16:01:34 +00:00
if ( size ! = pMemNode - > size ) {
2009-10-26 20:35:06 +00:00
// make sure memory object is discarded and not locked
2009-10-26 21:08:15 +00:00
assert ( pMemNode - > flags = = ( DWM_USED | DWM_DISCARDED ) ) ;
2009-10-26 16:01:34 +00:00
assert ( pMemNode - > size = = 0 ) ;
2008-07-23 09:02:47 +00:00
// unlink the mnode from the current heap
pMemNode - > pNext - > pPrev = pMemNode - > pPrev ;
pMemNode - > pPrev - > pNext = pMemNode - > pNext ;
// allocate a new node
2009-10-26 20:35:06 +00:00
pNew = MemoryAlloc ( size ) ;
2008-07-23 09:02:47 +00:00
// 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 ) ;
}
2009-10-26 20:38:34 +00:00
assert ( pMemNode - > pBaseAddr ) ;
2008-07-23 09:02:47 +00:00
}
2009-10-26 20:38:34 +00:00
/**
* 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 ;
}
2009-10-04 21:26:33 +00:00
} // End of namespace Tinsel