403 lines
16 KiB
C++
403 lines
16 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 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/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Memory cache manager
|
|
*/
|
|
|
|
#ifndef GLK_TADS_TADS2_MEMORY_CACHE
|
|
#define GLK_TADS_TADS2_MEMORY_CACHE
|
|
|
|
#include "glk/tads/tads2/lib.h"
|
|
#include "glk/tads/tads2/memory_cache_loader.h"
|
|
#include "glk/tads/tads2/memory_cache_swap.h"
|
|
#include "glk/tads/os_frob_tads.h"
|
|
|
|
namespace Glk {
|
|
namespace TADS {
|
|
namespace TADS2 {
|
|
|
|
/* if the os layer doesn't want long mcm macros, we don't either */
|
|
#ifdef OS_MCM_NO_MACRO
|
|
# define MCM_NO_MACRO
|
|
#endif
|
|
|
|
/*
|
|
* mcmon - cache object number. Each object in the cache is referenced
|
|
* by an object number, which is a number of this type.
|
|
*/
|
|
typedef ushort mcmon;
|
|
|
|
/* invalid object number - used to indicate N/A or no object */
|
|
#define MCMONINV ((mcmon)~0)
|
|
|
|
/* invalid page number */
|
|
#define MCMPINV ((uint)~0)
|
|
|
|
union mcmodefloc {
|
|
mcsseg mcmolocs; /* swap segment handle */
|
|
mclhd mcmolocl; /* load file object handle */
|
|
};
|
|
|
|
/*
|
|
* mcmodef - cache object definition. Each cache object is managed by
|
|
* this structure. Pointers for a doubly-linked list are provided;
|
|
* these are used to maintain a recent-use list for objects in memory,
|
|
* and to maintain a free list for cache object entries not in use. A
|
|
* set of flag bits is provided, as is the size of the object and its
|
|
* location (which is a memory pointer for objects in memory, a swap
|
|
* file handle for swapped-out objects, or a load-file handle for
|
|
* objects that are unloaded).
|
|
*/
|
|
struct mcmodef {
|
|
uchar *mcmoptr; /* memory pointer (if object is present) */
|
|
mcmodefloc mcmoloc; /* object's external location */
|
|
#define mcmoswh mcmoloc.mcmolocs
|
|
#define mcmoldh mcmoloc.mcmolocl
|
|
mcmon mcmonxt; /* next object in this list */
|
|
mcmon mcmoprv; /* previous object in this list */
|
|
ushort mcmoflg; /* flags for this cache object */
|
|
#define MCMOFDIRTY 0x01 /* object has been written */
|
|
#define MCMOFNODISC 0x02 /* not in load file (can't be discarded) */
|
|
#define MCMOFLOCK 0x04 /* object is locked */
|
|
#define MCMOFPRES 0x08 /* object is present in memory */
|
|
#define MCMOFLRU 0x10 /* object is in LRU chain */
|
|
#define MCMOFPAGE 0x20 /* object is a cache manager page */
|
|
#define MCMOFNOSWAP 0x40 /* object cannot be swapped out */
|
|
#define MCMOFFREE 0x80 /* entry refers to a free block of memory */
|
|
#define MCMOFREVRT 0x100 /* call revert callback upon loading */
|
|
uchar mcmolcnt; /* lock count */
|
|
ushort mcmosiz; /* size of the object */
|
|
};
|
|
|
|
/* heap header - allocate one of these in each heap */
|
|
struct mcmhdef {
|
|
mcmhdef *mcmhnxt; /* next heap in this chain */
|
|
};
|
|
|
|
/* GLOBAL cache manager context: tracks cache manager state */
|
|
struct mcmcx1def {
|
|
mcmodef **mcmcxtab; /* page table for the cache */
|
|
errcxdef *mcmcxerr; /* error handling context */
|
|
mcmhdef *mcmcxhpch; /* heap chain pointer */
|
|
mcscxdef mcmcxswc; /* swap manager context */
|
|
mclcxdef mcmcxldc; /* loader context */
|
|
ulong mcmcxmax; /* maximum amount of actual heap we can ever alloc */
|
|
mcmon mcmcxlru; /* least recently used object still in memory */
|
|
mcmon mcmcxmru; /* most recently used object */
|
|
mcmon mcmcxfre; /* head of free list */
|
|
mcmon mcmcxunu; /* head of unused list */
|
|
ushort mcmcxpage; /* last page table slot used */
|
|
ushort mcmcxpgmx; /* maximum number of pages we can allocate */
|
|
void (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg);
|
|
/* change swap handle in object to new swap handle */
|
|
};
|
|
|
|
/* CLIENT cache manager context: used by client to request mcm services */
|
|
struct mcmcxdef {
|
|
mcmcx1def *mcmcxgl; /* global cache manager context */
|
|
uint mcmcxflg; /* flags */
|
|
uint mcmcxmsz; /* maximum size of mapping table */
|
|
void (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr,
|
|
ushort siz); /* callback to load objects */
|
|
void *mcmcxldc; /* context for load callback */
|
|
void (*mcmcxrvf)(void *ctx, mcmon objn); /* revert object */
|
|
void *mcmcxrvc; /* context for revert callback */
|
|
mcmon *mcmcxmtb[1]; /* mapping table */
|
|
};
|
|
|
|
/* context flags */
|
|
#define MCMCXF_NO_PRP_DEL 0x0001 /* PRPFDEL is invalid in this game file */
|
|
|
|
/* convert from a client object number to a global object number */
|
|
/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */
|
|
#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255])
|
|
|
|
/*
|
|
* FREE LIST: this is a list, headed by context->mcmcxfre and chained
|
|
* forward and back by mcmonxt and mcmoprv, consisting of free memory
|
|
* blocks. These refer to blocks in the heap that are not used by any
|
|
* client objects.
|
|
*/
|
|
/*
|
|
* UNUSED LIST: this is a list, headed by context->mcmcxunu and chained
|
|
* forward by mcmonxt (not back, because it's never necessary to take
|
|
* anything out of the list except at the head, nor to search the list
|
|
* backwards), of unused cache object entries. These entries are not
|
|
* associated with any client object or with any heap memory. This list
|
|
* is used to get a new cache object header, and deleted cache objects
|
|
* are placed onto this list.
|
|
*/
|
|
/*
|
|
* LRU LIST: this is a list of in-memory blocks in ascending order of
|
|
* recency of use by the client. Each time a client unlocks a block,
|
|
* the block is moved to the most recent position in the list (the end
|
|
* of the list). To make it fast to add a new object, we keep a pointer
|
|
* to the end of the list as well as to the beginning. The start of the
|
|
* list is at context->mcmcxlru, and is the least recently unlocked
|
|
* block still in memory. The end of the list is at context->mcmcxmru,
|
|
* and is the most recently unlocked block.
|
|
*/
|
|
|
|
/*
|
|
* initialize the cache manager, returning a context for cache manager
|
|
* operations; a null pointer is returned if insufficient heap memory is
|
|
* available for initialization. The 'max' argument specifies the
|
|
* maximum amount of actual low-level heap memory that the cache manager
|
|
* can ever allocate on behalf of this context (of course, it can
|
|
* overcommit the heap through swapping). If 'max' is less than the
|
|
* size of a single heap allocation, it is adjusted upwards to that
|
|
* minimum.
|
|
*/
|
|
mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
|
|
osfildef *swapfp, char *swapfilename, errcxdef *errctx);
|
|
|
|
/* terminate the cache manager - frees the structure and all cache memory */
|
|
void mcmterm(mcmcx1def *ctx);
|
|
|
|
/* allocate a client context */
|
|
mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
|
|
void (*loadfn)(void *, mclhd, uchar *, ushort),
|
|
void *loadctx,
|
|
void (*revertfn)(void *, mcmon), void *revertctx);
|
|
|
|
/* terminate a client context - frees the structure memory */
|
|
void mcmcterm(mcmcxdef *ctx);
|
|
|
|
|
|
/*
|
|
* Lock a cache object, bringing it into memory if necessary. Returns
|
|
* a pointer to the memory containing the object. A null pointer is
|
|
* returned in case of error. The object remains fixed in memory at the
|
|
* returned location until unlocked. Locks are stacked; that is, if
|
|
* an object is locked twice in succession, it needs to be unlocked
|
|
* twice in succession before it is actually unlocked.
|
|
*/
|
|
#ifdef MCM_NO_MACRO
|
|
uchar *mcmlck(mcmcxdef *ctx, mcmon objnum);
|
|
#else /* MCM_NO_MACRO */
|
|
|
|
/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */
|
|
#define mcmlck(ctx,num) \
|
|
((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \
|
|
((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \
|
|
++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \
|
|
: mcmload(ctx,num))
|
|
|
|
#endif /* MCM_NO_MACRO */
|
|
|
|
/*
|
|
* Unlock a cache object, allowing it to be moved and swapped.
|
|
* Unlocking an object moves it to the end (i.e., most recently used)
|
|
* position on the LRU chain, making it the least favorable to swap out
|
|
* or discard. This happens at unlock time (rather than lock time)
|
|
* because presumably the client has been using the object the entire
|
|
* time it was locked. For this reason, and to keep memory unfragmented
|
|
* as much as possible, objects should not be kept locked except when
|
|
* actually in use. Note that locks nest; if an object is locked three
|
|
* times without an intervening unlock, it must be unlocked three times
|
|
* in a row. An object can be unlocked even if it's not locked; doing
|
|
* so has no effect.
|
|
*/
|
|
#ifdef MCM_NO_MACRO
|
|
void mcmunlck(mcmcxdef *ctx, mcmon objnum);
|
|
#else /* MCM_NO_MACRO */
|
|
|
|
/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */
|
|
#define mcmunlck(ctx,obj) \
|
|
((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
|
|
(--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \
|
|
((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \
|
|
mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0)
|
|
|
|
#endif /* MCM_NO_MACRO */
|
|
|
|
/*
|
|
* Allocate a new cache object. The new object is locked upon return.
|
|
* A pointer to the memory for the new object is returned, and
|
|
* the object number is returned at *nump. A null pointer is returned
|
|
* if the object cannot be allocated.
|
|
*/
|
|
/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */
|
|
#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE)
|
|
|
|
/*
|
|
* Reserve space for an object, giving it a particular client object
|
|
* number. This doesn't actually allocate any space for the object, but
|
|
* just sets it up so that it can be loaded by the client when it's
|
|
* needed.
|
|
*/
|
|
void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd);
|
|
|
|
/*
|
|
* Allocate a new cache object, and associate it with a particular
|
|
* client object number. An error is signalled if the client object
|
|
* number is already in use.
|
|
*/
|
|
/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */
|
|
#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE)
|
|
|
|
/*
|
|
* Reallocate an existing object. The object's size is increased
|
|
* or reduced according to newsize. The object is locked if it is
|
|
* not already, and the address of the object's memory is returned.
|
|
* Note that the object can move when reallocated, even if it was
|
|
* locked before the call.
|
|
*/
|
|
uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize);
|
|
|
|
/*
|
|
* Touch a cache object (rendering it dirty). When an object is
|
|
* written, the client must touch it to ensure that the version in
|
|
* memory is not discarded. The cache manager attempts to optimize
|
|
* activity by not writing objects that can be reconstructed from the
|
|
* load or swap file. Touching the object informs the cache manager
|
|
* that the object is different from any version it has in a swap or
|
|
* load file.
|
|
*/
|
|
/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */
|
|
#define mcmtch(ctx,obj) \
|
|
(mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY)
|
|
/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */
|
|
|
|
/* get size of a cache manager object - object need not be locked */
|
|
/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */
|
|
#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz)
|
|
|
|
/* determine if object has ever been touched */
|
|
/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */
|
|
#define mcmobjdirty(ctx, objn) \
|
|
(mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC))
|
|
|
|
/* get object's memory pointer - object must be locked for valid result */
|
|
/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */
|
|
#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr)
|
|
|
|
/*
|
|
* Free an object. The memory occupied by the object is discarded, and
|
|
* the object may no longer be referenced.
|
|
*/
|
|
void mcmfre(mcmcxdef *ctx, mcmon obj);
|
|
|
|
/*
|
|
* "Revert" an object - convert it back to original state. This
|
|
* routine just invokes a client callback to do the actual reversion
|
|
* work. The callback is called immediately if the object is already
|
|
* present in memory, but is deferred until the object is loaded/swapped
|
|
* in if the object is not in memory.
|
|
*/
|
|
/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */
|
|
#define mcmrevert(ctx, objn) \
|
|
((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \
|
|
((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \
|
|
: DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT))
|
|
|
|
/* get current size of object cache */
|
|
ulong mcmcsiz(mcmcxdef *ctx);
|
|
|
|
/* change an object's swap handle (used by swapper) */
|
|
/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */
|
|
#define mcmcsw(ctx, objn, swapn, oldswapn) \
|
|
((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn))
|
|
|
|
/* ------------------------------- PRIVATE ------------------------------- */
|
|
|
|
/* Unlock an object by its global handle */
|
|
#ifdef MCM_NO_MACRO
|
|
void mcmgunlck(mcmcx1def *ctx, mcmon objnum);
|
|
#else /* MCM_NO_MACRO */
|
|
|
|
/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */
|
|
#define mcmgunlck(ctx,obj) \
|
|
((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
|
|
(--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \
|
|
((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \
|
|
(void)0)
|
|
|
|
#endif /* MCM_NO_MACRO */
|
|
|
|
/* real memory allocator; clients use cover macros */
|
|
uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum,
|
|
int noclitrans);
|
|
|
|
/* free an object by global object number */
|
|
void mcmgfre(mcmcx1def *ctx, mcmon obj);
|
|
|
|
/* "use" an object (move to most-recent position in LRU chain) */
|
|
void mcmuse(mcmcx1def *ctx, mcmon n);
|
|
|
|
/*
|
|
* Load or swap in a cache object which is currently unloaded, locking
|
|
* it before returning. Returns a pointer to the memory containing
|
|
* the object, or a null pointer in case of error. The object
|
|
* remains fixed in memory at the returned location until unlocked.
|
|
*/
|
|
uchar *mcmload(mcmcxdef *ctx, mcmon objnum);
|
|
|
|
/*
|
|
* Size of each chunk of memory we'll request from the heap manager.
|
|
* To cut down on wasted memory from the heap manager, we'll always make
|
|
* our requests in this large size, then sub-allocate out of these
|
|
* chunks.
|
|
*/
|
|
#define MCMCHUNK 32768
|
|
|
|
/*
|
|
* number of cache entries in a page - make this a power of 2 to keep
|
|
* the arithmetic to find a cache object entry simple
|
|
*/
|
|
#define MCMPAGECNT 256
|
|
|
|
/*
|
|
* size of a page, in bytes
|
|
*/
|
|
#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef))
|
|
|
|
/*
|
|
* When allocating memory, and we find a free block satisfying the
|
|
* request, we will split the free block if doing so would result in
|
|
* enough space in the second block. MCMSPLIT specifies the minimum
|
|
* size left over that will allow a split to occur.
|
|
*/
|
|
#define MCMSPLIT 64
|
|
|
|
/* get an object cache entyr given a GLOBAL object number */
|
|
#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255]))
|
|
|
|
/* get an object cache entry given a CLIENT object number */
|
|
/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */
|
|
#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num))
|
|
|
|
/* allocate a block that will be locked for its entire lifetime */
|
|
void *mcmptralo(mcmcxdef *ctx, ushort siz);
|
|
|
|
/* free a block allocated with mcmptralo */
|
|
void mcmptrfre(mcmcxdef *ctx, void *block);
|
|
|
|
/* change an object's swap handle */
|
|
void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn);
|
|
|
|
} // End of namespace TADS2
|
|
} // End of namespace TADS
|
|
} // End of namespace Glk
|
|
|
|
#endif
|