302 lines
9.1 KiB
C++
302 lines
9.1 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/>.
|
|
*
|
|
*/
|
|
|
|
#include "glk/tads/tads2/memory_cache_swap.h"
|
|
#include "glk/tads/tads2/error.h"
|
|
#include "glk/tads/tads2/memory_cache.h"
|
|
#include "glk/tads/tads2/memory_cache_heap.h"
|
|
|
|
namespace Glk {
|
|
namespace TADS {
|
|
namespace TADS2 {
|
|
|
|
/* initialize swapper: allocate memory for swap page table */
|
|
void mcsini(mcscxdef *ctx, mcmcx1def *gmemctx, ulong maxsiz,
|
|
osfildef *fp, char *swapfilename, errcxdef *errctx) {
|
|
uchar *p;
|
|
|
|
ctx->mcscxtab = (mcsdsdef **)nullptr; /* anticipate failure */
|
|
|
|
/* allocate space from the low-level heap for page table and one page */
|
|
p = mchalo(errctx, ((MCSPAGETAB * sizeof(mcsdsdef *)) + (MCSPAGECNT * sizeof(mcsdsdef))), "mcsini");
|
|
|
|
/* set up the context with pointers to this chunk */
|
|
ctx->mcscxtab = (mcsdsdef **)p;
|
|
memset(p, 0, (size_t)(MCSPAGETAB * sizeof(mcsdsdef *)));
|
|
p += MCSPAGETAB * sizeof(mcsdsdef *);
|
|
ctx->mcscxtab[0] = (mcsdsdef *)p;
|
|
|
|
/* set up the rest of the context */
|
|
ctx->mcscxtop = (ulong)0;
|
|
ctx->mcscxmax = maxsiz;
|
|
ctx->mcscxmsg = 0;
|
|
ctx->mcscxfp = fp;
|
|
ctx->mcscxerr = errctx;
|
|
ctx->mcscxmem = gmemctx;
|
|
|
|
/*
|
|
* store the swap filename - make a copy so that the caller doesn't
|
|
* have to retain the original copy (in case it's on the stack)
|
|
*/
|
|
if (swapfilename != nullptr) {
|
|
size_t ln = strlen(swapfilename) + 1;
|
|
ctx->mcscxfname = (char *)mchalo(errctx, ln, "mcsini");
|
|
Common::strcpy_s(ctx->mcscxfname, ln, swapfilename);
|
|
} else {
|
|
ctx->mcscxfname = nullptr;
|
|
}
|
|
}
|
|
|
|
/* close the swapper */
|
|
void mcsclose(mcscxdef *ctx) {
|
|
if (ctx->mcscxtab)
|
|
mchfre(ctx->mcscxtab);
|
|
}
|
|
|
|
/*
|
|
* Attempt to compact the swap file when it grows too big. The segment
|
|
* descriptors are always allocated in increasing seek location within
|
|
* the swap file. To compress the file, make each descriptor's
|
|
* allocated size equal its used size for each in-use segment, and leave
|
|
* free segments at their allocated sizes.
|
|
*/
|
|
static void mcscompact(mcscxdef *ctx) {
|
|
char buf[512];
|
|
ulong max;
|
|
mcsseg cur_in;
|
|
mcsseg cur_out;
|
|
mcsdsdef *desc_in;
|
|
mcsdsdef *desc_out;
|
|
uint siz;
|
|
uint rdsiz;
|
|
ulong ptr_in;
|
|
ulong ptr_out;
|
|
|
|
max = 0; /* start at offset zero within file */
|
|
for (cur_in = cur_out = 0; cur_in < ctx->mcscxmsg; ++cur_in) {
|
|
desc_in = mcsdsc(ctx, cur_in);
|
|
|
|
/*
|
|
* If the present descriptor's address is wrong, and the swap
|
|
* segment is in use, move the swap segment. If it's not in
|
|
* use, we don't need to move it, because we're going to throw
|
|
* away the segment entirely.
|
|
*/
|
|
if (desc_in->mcsdsptr != max && (desc_in->mcsdsflg & MCSDSFINUSE)) {
|
|
/* ptr_in is the old location, ptr_out is the new location */
|
|
ptr_in = desc_in->mcsdsptr;
|
|
ptr_out = max;
|
|
|
|
/* copy through our buffer */
|
|
for (siz = desc_in->mcsdsosz; siz; siz -= rdsiz) {
|
|
/* size is whole buffer, or last piece if smaller */
|
|
rdsiz = (siz > sizeof(buf) ? sizeof(buf) : siz);
|
|
|
|
/* seek to old location and get the piece */
|
|
osfseek(ctx->mcscxfp, ptr_in, OSFSK_SET);
|
|
(void)osfrb(ctx->mcscxfp, buf, (size_t)rdsiz);
|
|
|
|
/* seek to new location and write the piece */
|
|
osfseek(ctx->mcscxfp, ptr_out, OSFSK_SET);
|
|
(void)osfwb(ctx->mcscxfp, buf, (size_t)rdsiz);
|
|
|
|
/* adjust the pointers by the size copied */
|
|
ptr_in += rdsiz;
|
|
ptr_out += rdsiz;
|
|
}
|
|
}
|
|
|
|
/* adjust object descriptor to reflect new location */
|
|
desc_in->mcsdsptr = max;
|
|
|
|
/*
|
|
* Make current object's size exact if it's in use. If it's
|
|
* not in use, delete the segment altogether.
|
|
*/
|
|
if (desc_in->mcsdsflg & MCSDSFINUSE) {
|
|
desc_in->mcsdssiz = desc_in->mcsdsosz;
|
|
max += desc_in->mcsdssiz;
|
|
|
|
/* copy descriptor to correct position to close any holes */
|
|
if (cur_out != cur_in) {
|
|
desc_out = mcsdsc(ctx, cur_out);
|
|
OSCPYSTRUCT(*desc_out, *desc_in);
|
|
|
|
/* we need to renumber the corresponding object as well */
|
|
mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
|
|
cur_out, cur_in);
|
|
}
|
|
|
|
/* we actually wrote this one, so move output pointer */
|
|
++cur_out;
|
|
} else {
|
|
/*
|
|
* We need to renumber the corresponding object so that it
|
|
* knows there is no swap segment for it any more.
|
|
*/
|
|
mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
|
|
MCSSEGINV, cur_in);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Adjust the top of the file for our new size, and add the savings
|
|
* into the available space counter. Also, adjust the total handle
|
|
* count to reflect any descriptors that we've deleted.
|
|
*/
|
|
ctx->mcscxmax += (ctx->mcscxtop - max);
|
|
ctx->mcscxtop = max;
|
|
ctx->mcscxmsg = cur_out;
|
|
}
|
|
|
|
/* swap an object out to the swap file */
|
|
mcsseg mcsout(mcscxdef *ctx, uint objid, uchar *ptr, ushort siz,
|
|
mcsseg oldseg, int dirty) {
|
|
mcsdsdef *desc;
|
|
mcsdsdef **pagep;
|
|
uint i;
|
|
uint j;
|
|
mcsseg min;
|
|
mcsseg cur;
|
|
ushort minsiz = 0;
|
|
|
|
IF_DEBUG(debug(10, "<< mcsout: objid=%d, ptr=%lx, siz=%u, oldseg=%u >>\n",
|
|
objid, (unsigned long)ptr, siz, oldseg));
|
|
|
|
/* see if old segment can be reused */
|
|
if (oldseg != MCSSEGINV) {
|
|
desc = mcsdsc(ctx, oldseg);
|
|
if (!(desc->mcsdsflg & MCSDSFINUSE) /* if old seg is not in use */
|
|
&& desc->mcsdsobj == objid /* and it has same object */
|
|
&& desc->mcsdssiz >= siz /* and it's still big enough */
|
|
&& !dirty) /* and the object in memory hasn't been changed */
|
|
{
|
|
/* we can reuse the old segment without rewriting it */
|
|
desc->mcsdsflg |= MCSDSFINUSE; /* mark segment as in use */
|
|
return (oldseg);
|
|
}
|
|
}
|
|
|
|
/* look for the smallest unused segment big enough for this object */
|
|
for (cur = 0, min = MCSSEGINV, i = 0, pagep = ctx->mcscxtab; cur < ctx->mcscxmsg && i < MCSPAGETAB && *pagep; ++pagep, ++i) {
|
|
for (j = 0, desc = *pagep; cur < ctx->mcscxmsg && j < MCSPAGECNT; ++desc, ++j, ++cur) {
|
|
if (!(desc->mcsdsflg & MCSDSFINUSE) && desc->mcsdssiz >= siz && (min == MCSSEGINV || desc->mcsdssiz < minsiz)) {
|
|
min = cur;
|
|
minsiz = desc->mcsdssiz;
|
|
if (minsiz == siz)
|
|
break; /* exact match - we're done */
|
|
}
|
|
}
|
|
/* quit if we found an exact match */
|
|
if (min != MCSSEGINV && minsiz == siz)
|
|
break;
|
|
}
|
|
|
|
/* if we found nothing, allocate a new segment if possible */
|
|
if (min == MCSSEGINV) {
|
|
if (siz > ctx->mcscxmax) {
|
|
/* swap file is too big; compact it and try again */
|
|
mcscompact(ctx);
|
|
if (siz > ctx->mcscxmax)
|
|
errsig(ctx->mcscxerr, ERR_SWAPBIG);
|
|
}
|
|
|
|
min = ctx->mcscxmsg;
|
|
|
|
if (!ctx->mcscxtab[min >> 8]) /* haven't allocate page yet? */
|
|
{
|
|
ctx->mcscxtab[min >> 8] =
|
|
(mcsdsdef *)mchalo(ctx->mcscxerr,
|
|
(MCSPAGECNT * sizeof(mcsdsdef)),
|
|
"mcsout");
|
|
}
|
|
|
|
/* set up new descriptor */
|
|
desc = mcsdsc(ctx, min);
|
|
desc->mcsdsptr = ctx->mcscxtop;
|
|
desc->mcsdssiz = siz;
|
|
desc->mcsdsobj = objid;
|
|
|
|
/* write out the segment */
|
|
mcswrt(ctx, desc, ptr, siz);
|
|
desc->mcsdsflg = MCSDSFINUSE;
|
|
|
|
/* update context information to account for new segment */
|
|
ctx->mcscxtop += siz; /* add to top seek offset in file */
|
|
ctx->mcscxmax -= siz; /* take size out of quota */
|
|
ctx->mcscxmsg++; /* increment last segment allocated */
|
|
|
|
return (min);
|
|
} else {
|
|
desc = mcsdsc(ctx, min);
|
|
desc->mcsdsobj = objid;
|
|
mcswrt(ctx, desc, ptr, siz);
|
|
desc->mcsdsflg |= MCSDSFINUSE;
|
|
|
|
return (min);
|
|
}
|
|
}
|
|
|
|
void mcsin(mcscxdef *ctx, mcsseg seg, uchar *ptr, ushort siz) {
|
|
mcsdsdef *desc = mcsdsc(ctx, seg);
|
|
|
|
IF_DEBUG(debug(10, "<< mcsin: seg=%u, ptr=%lx, siz=%d, objid=%u >>\n",
|
|
seg, (unsigned long)ptr, siz, desc->mcsdsobj));
|
|
|
|
assert(seg < ctx->mcscxmsg);
|
|
|
|
/* can only swap in as much as we wrote */
|
|
if (desc->mcsdsosz < siz)
|
|
siz = desc->mcsdsosz;
|
|
|
|
/* seek to and read the segment */
|
|
if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
|
|
errsig(ctx->mcscxerr, ERR_FSEEK);
|
|
if (osfrb(ctx->mcscxfp, ptr, (size_t)siz))
|
|
errsig(ctx->mcscxerr, ERR_FREAD);
|
|
|
|
desc->mcsdsflg &= ~MCSDSFINUSE; /* segment no longer in use */
|
|
}
|
|
|
|
void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl) {
|
|
int tries;
|
|
|
|
desc->mcsdsosz = bufl;
|
|
|
|
for (tries = 0; tries < 2; ++tries) {
|
|
/* attempt to write the object to the swap file */
|
|
if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
|
|
errsig(ctx->mcscxerr, ERR_FSEEK);
|
|
if (!osfwb(ctx->mcscxfp, buf, (size_t)bufl))
|
|
return;
|
|
|
|
/* couldn't write it; compact the swap file */
|
|
mcscompact(ctx);
|
|
}
|
|
|
|
/* couldn't write to swap file, even after compacting it */
|
|
errsig(ctx->mcscxerr, ERR_FWRITE);
|
|
}
|
|
|
|
} // End of namespace TADS2
|
|
} // End of namespace TADS
|
|
} // End of namespace Glk
|