mirror of
https://github.com/microsoft/Microsoft-3D-Movie-Maker.git
synced 2024-11-22 02:12:33 +01:00
2404 lines
59 KiB
C++
2404 lines
59 KiB
C++
/* Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT License. */
|
|
|
|
/***************************************************************************
|
|
Author: ShonK
|
|
Project: Kauai
|
|
Reviewed:
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Basic collection classes:
|
|
General List (GL), Allocated List (AL),
|
|
General Group (GG), Allocated Group (AG),
|
|
General String Table (GST), Allocated String Table (AST).
|
|
|
|
BASE ---> GRPB -+-> GLB -+-> GL
|
|
| +-> AL
|
|
|
|
|
+-> GGB -+-> GG
|
|
| +-> AG
|
|
|
|
|
+-> GSTB-+-> GST
|
|
+-> AST
|
|
|
|
***************************************************************************/
|
|
#include "util.h"
|
|
ASSERTNAME
|
|
|
|
|
|
RTCLASS(GRPB)
|
|
RTCLASS(GLB)
|
|
RTCLASS(GL)
|
|
RTCLASS(AL)
|
|
RTCLASS(GGB)
|
|
RTCLASS(GG)
|
|
RTCLASS(AG)
|
|
|
|
|
|
/***************************************************************************
|
|
GRPB: Manages two sections of data. Currently the two sections are
|
|
in two separate hq's, but they could be in one without affecting the
|
|
clients. The actual data in the two sections is determined by the
|
|
subclass (client). This class just manages resizing the data sections.
|
|
***************************************************************************/
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for GRPB. Frees the hq.
|
|
***************************************************************************/
|
|
GRPB::~GRPB(void)
|
|
{
|
|
AssertThis(0);
|
|
FreePhq(&_hqData1);
|
|
FreePhq(&_hqData2);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Ensure that the two sections are at least the given cb's large.
|
|
if (grfgrp & fgrpShrink), makes them exact.
|
|
***************************************************************************/
|
|
bool GRPB::_FEnsureSizes(long cbMin1, long cbMin2, ulong grfgrp)
|
|
{
|
|
AssertThis(0);
|
|
Assert(cbMin1 >= 0 && cbMin2 >= 0, "negative sizes");
|
|
|
|
if (grfgrp & fgrpShrink)
|
|
{
|
|
//shrink anything that's too big
|
|
if (cbMin1 == 0)
|
|
{
|
|
FreePhq(&_hqData1);
|
|
_cb1 = 0;
|
|
}
|
|
else if (cbMin1 < _cb1)
|
|
{
|
|
FResizePhq(&_hqData1, cbMin1, fmemNil, mprNormal);
|
|
_cb1 = cbMin1;
|
|
}
|
|
|
|
if (cbMin2 == 0)
|
|
{
|
|
FreePhq(&_hqData2);
|
|
_cb2 = 0;
|
|
}
|
|
else if (cbMin2 < _cb2)
|
|
{
|
|
FResizePhq(&_hqData2, cbMin2, fmemNil, mprNormal);
|
|
_cb2 = cbMin2;
|
|
}
|
|
}
|
|
|
|
if (cbMin1 > _cb1 && !_FEnsureHqCb(&_hqData1, cbMin1, _cbMinGrow1, &_cb1))
|
|
return fFalse;
|
|
if (cbMin2 > _cb2 && !_FEnsureHqCb(&_hqData2, cbMin2, _cbMinGrow2, &_cb2))
|
|
return fFalse;
|
|
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Ensure that the given HQ is large enough.
|
|
***************************************************************************/
|
|
bool GRPB::_FEnsureHqCb(HQ *phq, long cb, long cbMinGrow, long *pcb)
|
|
{
|
|
AssertVarMem(phq);
|
|
AssertIn(cbMinGrow, 0, kcbMax);
|
|
AssertVarMem(pcb);
|
|
AssertIn(*pcb, 0, kcbMax);
|
|
|
|
// limit the size
|
|
if ((ulong)cb >= kcbMax)
|
|
return fFalse;
|
|
|
|
AssertIn(cb, *pcb + 1, kcbMax);
|
|
if (hqNil != *phq)
|
|
{
|
|
//resize an existing hq
|
|
AssertHq(*phq);
|
|
|
|
if ((cbMinGrow += *pcb) > cb && FResizePhq(phq, cbMinGrow,
|
|
fmemNil, mprForSpeed))
|
|
{
|
|
*pcb = cbMinGrow;
|
|
return fTrue;
|
|
}
|
|
else if (FResizePhq(phq, cb, fmemNil, mprNormal))
|
|
{
|
|
*pcb = cb;
|
|
return fTrue;
|
|
}
|
|
return fFalse;
|
|
}
|
|
|
|
// just allocate the thing
|
|
Assert(*pcb == 0, "bad cb");
|
|
if (cbMinGrow > cb && FAllocHq(phq, cbMinGrow, fmemNil, mprForSpeed))
|
|
{
|
|
*pcb = cbMinGrow;
|
|
return fTrue;
|
|
}
|
|
else if (FAllocHq(phq, cb, fmemNil, mprNormal))
|
|
{
|
|
*pcb = cb;
|
|
return fTrue;
|
|
}
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Make the given GRPB a duplicate of this one.
|
|
***************************************************************************/
|
|
bool GRPB::_FDup(PGRPB pgrpbDst, long cb1, long cb2)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pgrpbDst, 0);
|
|
AssertIn(cb1, 0, kcbMax);
|
|
AssertIn(cb2, 0, kcbMax);
|
|
|
|
if (!pgrpbDst->_FEnsureSizes(cb1, cb2, fgrpShrink))
|
|
return fFalse;
|
|
|
|
if (cb1 > 0)
|
|
CopyPb(_Qb1(0), pgrpbDst->_Qb1(0), cb1);
|
|
if (cb2 > 0)
|
|
CopyPb(_Qb2(0), pgrpbDst->_Qb2(0), cb2);
|
|
|
|
pgrpbDst->_cbMinGrow1 = _cbMinGrow1;
|
|
pgrpbDst->_cbMinGrow2 = _cbMinGrow2;
|
|
pgrpbDst->_ivMac = _ivMac;
|
|
|
|
AssertPo(pgrpbDst, 0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write a group to a flo.
|
|
***************************************************************************/
|
|
bool GRPB::FWriteFlo(PFLO pflo, short bo, short osk)
|
|
{
|
|
BLCK blck(pflo);
|
|
return FWrite(&blck, bo, osk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the GRPB data to the block. First write the (pv, cb), then
|
|
cb1 bytes from the first section and cb2 bytes from the second.
|
|
***************************************************************************/
|
|
bool GRPB::_FWrite(PBLCK pblck, void *pv, long cb, long cb1, long cb2)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertIn(cb, 1, kcbMax);
|
|
AssertPvCb(pv, cb);
|
|
AssertIn(cb1, 0, _cb1 + 1);
|
|
AssertIn(cb2, 0, _cb2 + 1);
|
|
|
|
bool fRet = fFalse;
|
|
|
|
if (pblck->Cb() != cb + cb1 + cb2)
|
|
{
|
|
Bug("blck wrong size");
|
|
return fFalse;
|
|
}
|
|
if (!pblck->FWriteRgb(pv, cb, 0))
|
|
return fFalse;
|
|
|
|
if (cb1 > 0)
|
|
{
|
|
fRet = pblck->FWriteRgb(PvLockHq(_hqData1), cb1, cb);
|
|
UnlockHq(_hqData1);
|
|
if (!fRet)
|
|
return fFalse;
|
|
}
|
|
if (cb2 > 0)
|
|
{
|
|
fRet = pblck->FWriteRgb(PvLockHq(_hqData2), cb2, cb + cb1);
|
|
UnlockHq(_hqData2);
|
|
if (!fRet)
|
|
return fFalse;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read the two sections of data from the given location in the given
|
|
block.
|
|
***************************************************************************/
|
|
bool GRPB::_FReadData(PBLCK pblck, long cb1, long cb2, long ib)
|
|
{
|
|
AssertPo(pblck, fblckUnpacked);
|
|
AssertIn(cb1, 0, kcbMax);
|
|
AssertIn(cb2, 0, kcbMax);
|
|
|
|
bool fRet;
|
|
|
|
if (cb1 == 0 && cb2 == 0)
|
|
return fTrue;
|
|
if (!_FEnsureSizes(cb1, cb2, fgrpNil))
|
|
return fFalse;
|
|
|
|
if (cb1 > 0)
|
|
{
|
|
fRet = pblck->FReadRgb(PvLockHq(_hqData1), cb1, ib);
|
|
UnlockHq(_hqData1);
|
|
if (!fRet)
|
|
return fFalse;
|
|
}
|
|
if (cb2 > 0)
|
|
{
|
|
fRet = pblck->FReadRgb(PvLockHq(_hqData2), cb2, ib + cb1);
|
|
UnlockHq(_hqData2);
|
|
if (!fRet)
|
|
return fFalse;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of the grpb stuff.
|
|
***************************************************************************/
|
|
void GRPB::AssertValid(ulong grfobj)
|
|
{
|
|
GRPB_PAR::AssertValid(grfobj | fobjAllocated);
|
|
AssertIn(_cb1, 0, kcbMax);
|
|
AssertIn(_cb2, 0, kcbMax);
|
|
Assert((_cb1 == 0) == (_hqData1 == hqNil),
|
|
"cb's don't match _hqData1");
|
|
Assert((_cb2 == 0) == (_hqData2 == hqNil),
|
|
"cb's don't match _hqData2");
|
|
Assert(_hqData1 == hqNil || CbOfHq(_hqData1) == _cb1,
|
|
"_hqData1 wrong size");
|
|
Assert(_hqData2 == hqNil || CbOfHq(_hqData2) == _cb2,
|
|
"_hqData2 wrong size");
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Mark the _hqData blocks.
|
|
***************************************************************************/
|
|
void GRPB::MarkMem(void)
|
|
{
|
|
AssertThis(0);
|
|
GRPB_PAR::MarkMem();
|
|
MarkHq(_hqData1);
|
|
MarkHq(_hqData2);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
GLB: Base class for GL (general list) and AL (general allocated list).
|
|
The list data goes in section 1. The GL class doesn't use section 2.
|
|
The AL class uses section 2 for a bit array indicating whether an entry
|
|
is free or in use.
|
|
***************************************************************************/
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for the list base.
|
|
***************************************************************************/
|
|
GLB::GLB(long cb)
|
|
{
|
|
AssertIn(cb, 0, kcbMax);
|
|
_cbEntry = cb;
|
|
|
|
// use some reasonable values for _cbMinGrow* - code can always set
|
|
// set these to something else
|
|
_cbMinGrow1 = 128;
|
|
_cbMinGrow2 = 16;
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return a volatile pointer to a list entry.
|
|
NOTE: don't assert !FFree(iv) for allocated lists.
|
|
***************************************************************************/
|
|
void *GLB::QvGet(long iv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac + 1);
|
|
return (0 == _ivMac) ? pvNil : _Qb1(LwMul(iv, _cbEntry));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get the data for the iv'th element in the GLB.
|
|
***************************************************************************/
|
|
void GLB::Get(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
AssertPvCb(pv, _cbEntry);
|
|
CopyPb(QvGet(iv), pv, _cbEntry);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Put data into the iv'th element in the GLB.
|
|
***************************************************************************/
|
|
void GLB::Put(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
AssertPvCb(pv, _cbEntry);
|
|
CopyPb(pv, QvGet(iv), _cbEntry);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Lock the data and return a pointer to the ith item.
|
|
***************************************************************************/
|
|
void *GLB::PvLock(long iv)
|
|
{
|
|
Lock();
|
|
return QvGet(iv);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the minimum that a GL should grow by.
|
|
***************************************************************************/
|
|
void GLB::SetMinGrow(long cvAdd)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cvAdd, 0, kcbMax);
|
|
|
|
_cbMinGrow1 = CbRoundToLong(LwMul(cvAdd, _cbEntry));
|
|
_cbMinGrow2 = CbRoundToLong(LwDivAway(cvAdd, 8));
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a list (GL or AL).
|
|
***************************************************************************/
|
|
void GLB::AssertValid(ulong grfobj)
|
|
{
|
|
GLB_PAR::AssertValid(grfobj);
|
|
AssertIn(_cbEntry, 1, kcbMax);
|
|
AssertIn(_ivMac, 0, kcbMax);
|
|
Assert(_Cb1() >= LwMul(_cbEntry, _ivMac), "array area too small");
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new list and ensure that it has space for cvInit elements.
|
|
***************************************************************************/
|
|
PGL GL::PglNew(long cb, long cvInit)
|
|
{
|
|
AssertIn(cb, 1, kcbMax);
|
|
AssertIn(cvInit, 0, kcbMax);
|
|
PGL pgl;
|
|
|
|
if ((pgl = NewObj GL(cb)) == pvNil)
|
|
return pvNil;
|
|
if (cvInit > 0 && !pgl->FEnsureSpace(cvInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&pgl);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgl, 0);
|
|
return pgl;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a list from a block and return it.
|
|
***************************************************************************/
|
|
PGL GL::PglRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
PGL pgl;
|
|
|
|
/* the use of 4 for the cb is bogus, but _FRead overwrites the cb anyway */
|
|
if ((pgl = NewObj GL(4)) == pvNil)
|
|
goto LFail;
|
|
if (!pgl->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&pgl);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgl, 0);
|
|
return pgl;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a list from file and return it.
|
|
***************************************************************************/
|
|
PGL GL::PglRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck(pfil, fp, cb);
|
|
return PglRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for GL.
|
|
***************************************************************************/
|
|
GL::GL(long cb) : GLB(cb)
|
|
{
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Provided for completeness (all GRPB's have an FFree routine).
|
|
Returns false iff iv is a valid index for the GL.
|
|
***************************************************************************/
|
|
bool GL::FFree(long iv)
|
|
{
|
|
AssertThis(0);
|
|
return !FIn(iv, 0, _ivMac);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this GL.
|
|
***************************************************************************/
|
|
PGL GL::PglDup(void)
|
|
{
|
|
AssertThis(0);
|
|
PGL pgl;
|
|
|
|
if (pvNil == (pgl = PglNew(_cbEntry)))
|
|
return pvNil;
|
|
|
|
if (!_FDup(pgl, LwMul(_ivMac, _cbEntry), 0))
|
|
ReleasePpo(&pgl);
|
|
|
|
AssertNilOrPo(pgl, 0);
|
|
return pgl;
|
|
}
|
|
|
|
|
|
//List on file
|
|
struct GLF
|
|
{
|
|
short bo;
|
|
short osk;
|
|
long cbEntry;
|
|
long ivMac;
|
|
};
|
|
const BOM kbomGlf = 0x5F000000L;
|
|
|
|
|
|
/***************************************************************************
|
|
Return the amount of space on file needed for the list.
|
|
***************************************************************************/
|
|
long GL::CbOnFile(void)
|
|
{
|
|
AssertThis(0);
|
|
return size(GLF) + LwMul(_cbEntry, _ivMac);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the list to disk.
|
|
***************************************************************************/
|
|
bool GL::FWrite(PBLCK pblck, short bo, short osk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
Assert(kboCur == bo || kboOther == bo, "bad bo");
|
|
AssertOsk(osk);
|
|
|
|
GLF glf;
|
|
|
|
glf.bo = kboCur;
|
|
glf.osk = osk;
|
|
glf.cbEntry = _cbEntry;
|
|
glf.ivMac = _ivMac;
|
|
if (kboOther == bo)
|
|
{
|
|
SwapBytesBom(&glf, kbomGlf);
|
|
Assert(glf.bo == bo, "wrong bo");
|
|
Assert(glf.osk == osk, "osk not invariant under byte swapping");
|
|
}
|
|
return _FWrite(pblck, &glf, size(glf), LwMul(_cbEntry, _ivMac), 0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read list data from disk.
|
|
***************************************************************************/
|
|
bool GL::_FRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
GLF glf;
|
|
long cb;
|
|
bool fRet = fFalse;
|
|
|
|
if (!pblck->FUnpackData())
|
|
goto LFail;
|
|
|
|
cb = pblck->Cb();
|
|
if (cb < size(glf))
|
|
goto LBug;
|
|
|
|
if (!pblck->FReadRgb(&glf, size(glf), 0))
|
|
goto LFail;
|
|
|
|
if (pbo != pvNil)
|
|
*pbo = glf.bo;
|
|
if (posk != pvNil)
|
|
*posk = glf.osk;
|
|
|
|
if (glf.bo == kboOther)
|
|
SwapBytesBom(&glf, kbomGlf);
|
|
|
|
cb -= size(glf);
|
|
if (glf.bo != kboCur || glf.cbEntry <= 0 || glf.ivMac < 0 ||
|
|
cb != glf.cbEntry * glf.ivMac)
|
|
{
|
|
LBug:
|
|
Warn("file corrupt or not a GL");
|
|
goto LFail;
|
|
}
|
|
|
|
_cbEntry = glf.cbEntry;
|
|
_ivMac = glf.ivMac;
|
|
fRet = _FReadData(pblck, cb, 0, size(glf));
|
|
|
|
LFail:
|
|
TrashVarIf(!fRet, pbo);
|
|
TrashVarIf(!fRet, posk);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Insert some items into a list at position iv. iv should be <= IvMac().
|
|
***************************************************************************/
|
|
bool GL::FInsert(long iv, void *pv, long cv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac + 1);
|
|
AssertIn(cv, 1, kcbMax);
|
|
AssertNilOrPvCb(pv, LwMul(cv, _cbEntry));
|
|
|
|
byte *qb;
|
|
long cbTot, cbIns, ibIns;
|
|
|
|
cbTot = LwMul(_ivMac + cv, _cbEntry);
|
|
cbIns = LwMul(cv, _cbEntry);
|
|
ibIns = LwMul(iv, _cbEntry);
|
|
if (cbTot > _Cb1() && !_FEnsureSizes(cbTot, 0, fgrpNil))
|
|
return fFalse;
|
|
|
|
qb = _Qb1(ibIns);
|
|
if (iv < _ivMac)
|
|
BltPb(qb, qb + cbIns, cbTot - cbIns - ibIns);
|
|
if (pvNil != pv)
|
|
CopyPb(pv, qb, cbIns);
|
|
_ivMac += cv;
|
|
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete an element from the list. This changes the indices of all
|
|
later elements.
|
|
***************************************************************************/
|
|
void GL::Delete(long iv)
|
|
{
|
|
AssertThis(0);
|
|
Delete(iv, 1);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete a range of elements. This changes the indices of all later
|
|
elements.
|
|
***************************************************************************/
|
|
void GL::Delete(long ivMin, long cv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(ivMin, 0, _ivMac);
|
|
AssertIn(cv, 1, _ivMac - ivMin + 1);
|
|
|
|
if (ivMin < (_ivMac -= cv))
|
|
{
|
|
byte *qb = _Qb1(LwMul(ivMin, _cbEntry));
|
|
BltPb(qb + LwMul(cv, _cbEntry), qb, LwMul(_ivMac - ivMin, _cbEntry));
|
|
}
|
|
TrashPvCb(_Qb1(LwMul(_ivMac, _cbEntry)), LwMul(cv, _cbEntry));
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move the entry at ivSrc to be immediately before the element that is
|
|
currently at ivTarget. If ivTarget > ivSrc, the entry actually ends
|
|
up at (ivTarget - 1) and the entry at ivTarget doesn't move. If
|
|
ivTarget < ivSrc, the entry ends up at ivTarget and the entry at
|
|
ivTarget moves to (ivTarget + 1). Everything in between is shifted
|
|
appropriately. ivTarget is allowed to be equal to IvMac().
|
|
***************************************************************************/
|
|
void GL::Move(long ivSrc, long ivTarget)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(ivSrc, 0, _ivMac);
|
|
AssertIn(ivTarget, 0, _ivMac + 1);
|
|
|
|
MoveElement(_Qb1(0), _cbEntry, ivSrc, ivTarget);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Add an element to the end of the list. Returns the location in *piv.
|
|
On failure, returns false and *piv is undefined.
|
|
***************************************************************************/
|
|
bool GL::FAdd(void *pv, long *piv)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrVarMem(piv);
|
|
|
|
if (piv != pvNil)
|
|
*piv = _ivMac;
|
|
if (!FInsert(_ivMac, pv))
|
|
{
|
|
TrashVar(piv);
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Stack operation. Returns fFalse on stack underflow.
|
|
***************************************************************************/
|
|
bool GL::FPop(void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPvCb(pv, _cbEntry);
|
|
|
|
if (_ivMac == 0)
|
|
{
|
|
TrashPvCb(pv, _cbEntry);
|
|
return fFalse;
|
|
}
|
|
if (pv != pvNil)
|
|
Get(_ivMac - 1, pv);
|
|
_ivMac--;
|
|
TrashPvCb(_Qb1(LwMul(_ivMac, _cbEntry)), _cbEntry);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the number of elements. Used rarely (to add a block of elements
|
|
at a time or to "zero out" a list.
|
|
***************************************************************************/
|
|
bool GL::FSetIvMac(long ivMacNew)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(ivMacNew, 0, kcbMax);
|
|
long cb;
|
|
|
|
if (ivMacNew > _ivMac)
|
|
{
|
|
if (ivMacNew > kcbMax / _cbEntry)
|
|
{
|
|
Bug("who's trying to allocate a list this big?");
|
|
return fFalse;
|
|
}
|
|
|
|
cb = LwMul(ivMacNew, _cbEntry);
|
|
if (cb > _Cb1() && !_FEnsureSizes(cb, 0, fgrpNil))
|
|
return fFalse;
|
|
TrashPvCb(_Qb1(LwMul(_ivMac, _cbEntry)),
|
|
LwMul(ivMacNew - _ivMac, _cbEntry));
|
|
}
|
|
#ifdef DEBUG
|
|
else if (ivMacNew < _ivMac)
|
|
{
|
|
TrashPvCb(_Qb1(LwMul(ivMacNew, _cbEntry)),
|
|
LwMul(_ivMac - ivMacNew, _cbEntry));
|
|
}
|
|
#endif //DEBUG
|
|
|
|
_ivMac = ivMacNew;
|
|
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Make sure there is room for at least cvAdd additional entries. If
|
|
fgrpShrink is set, will shrink the list if it has more than cvAdd
|
|
available entries.
|
|
***************************************************************************/
|
|
bool GL::FEnsureSpace(long cvAdd, ulong grfgrp)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cvAdd, 0, kcbMax);
|
|
|
|
// limit the size of the list
|
|
if (cvAdd > kcbMax / _cbEntry - _ivMac)
|
|
{
|
|
Bug("who's trying to allocate a list this big?");
|
|
return fFalse;
|
|
}
|
|
|
|
return _FEnsureSizes(LwMul(cvAdd + _ivMac, _cbEntry), 0, grfgrp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new allocated list and ensure that it has space for
|
|
cvInit elements.
|
|
***************************************************************************/
|
|
PAL AL::PalNew(long cb, long cvInit)
|
|
{
|
|
AssertIn(cb, 1, kcbMax);
|
|
AssertIn(cvInit, 0, kcbMax);
|
|
PAL pal;
|
|
|
|
if ((pal = NewObj AL(cb)) == pvNil)
|
|
return pvNil;
|
|
if (cvInit > 0 && !pal->FEnsureSpace(cvInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&pal);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pal, 0);
|
|
return pal;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated list from the block and return it.
|
|
***************************************************************************/
|
|
PAL AL::PalRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
PAL pal;
|
|
|
|
/* the use of 4 for the cb is bogus, but _FRead overwrites the cb anyway */
|
|
if ((pal = NewObj AL(4)) == pvNil)
|
|
goto LFail;
|
|
if (!pal->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&pal);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pal, 0);
|
|
return pal;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated list from file and return it.
|
|
***************************************************************************/
|
|
PAL AL::PalRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck(pfil, fp, cb);
|
|
return PalRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for AL (allocated list) class.
|
|
***************************************************************************/
|
|
AL::AL(long cb) : GLB(cb)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this AL.
|
|
***************************************************************************/
|
|
PAL AL::PalDup(void)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
PAL pal;
|
|
|
|
if (pvNil == (pal = PalNew(_cbEntry)))
|
|
return pvNil;
|
|
|
|
if (!_FDup(pal, LwMul(_ivMac, _cbEntry), CbFromCbit(_ivMac)))
|
|
ReleasePpo(&pal);
|
|
else
|
|
pal->_cvFree = _cvFree;
|
|
|
|
AssertNilOrPo(pal, fobjAssertFull);
|
|
return pal;
|
|
}
|
|
|
|
|
|
//Allocated list on file
|
|
struct ALF
|
|
{
|
|
short bo;
|
|
short osk;
|
|
long cbEntry;
|
|
long ivMac;
|
|
long cvFree;
|
|
};
|
|
const BOM kbomAlf = 0x5FC00000L;
|
|
|
|
|
|
/***************************************************************************
|
|
Return the amount of space on file needed for the list.
|
|
***************************************************************************/
|
|
long AL::CbOnFile(void)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
|
|
return size(ALF) + LwMul(_cbEntry, _ivMac) + CbFromCbit(_ivMac);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the list to disk.
|
|
***************************************************************************/
|
|
bool AL::FWrite(PBLCK pblck, short bo, short osk)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pblck, 0);
|
|
Assert(kboCur == bo || kboOther == bo, "bad bo");
|
|
AssertOsk(osk);
|
|
|
|
ALF alf;
|
|
|
|
alf.bo = kboCur;
|
|
alf.osk = osk;
|
|
alf.cbEntry = _cbEntry;
|
|
alf.ivMac = _ivMac;
|
|
alf.cvFree = _cvFree;
|
|
if (kboOther == bo)
|
|
{
|
|
SwapBytesBom(&alf, kbomAlf);
|
|
Assert(alf.bo == bo, "wrong bo");
|
|
Assert(alf.osk == osk, "osk not invariant under byte swapping");
|
|
}
|
|
return _FWrite(pblck, &alf, size(alf), LwMul(_cbEntry, _ivMac),
|
|
CbFromCbit(_ivMac));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read allocated list data from the block.
|
|
***************************************************************************/
|
|
bool AL::_FRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
ALF alf;
|
|
long cbT;
|
|
long cb;
|
|
bool fRet = fFalse;
|
|
|
|
if (!pblck->FUnpackData())
|
|
goto LFail;
|
|
|
|
cb = pblck->Cb();
|
|
if (cb < size(alf))
|
|
goto LBug;
|
|
|
|
if (!pblck->FReadRgb(&alf, size(alf), 0))
|
|
goto LFail;
|
|
|
|
if (pbo != pvNil)
|
|
*pbo = alf.bo;
|
|
if (posk != pvNil)
|
|
*posk = alf.osk;
|
|
|
|
if (alf.bo == kboOther)
|
|
SwapBytesBom(&alf, kbomAlf);
|
|
|
|
cb -= size(alf);
|
|
cbT = alf.cbEntry * alf.ivMac;
|
|
if (alf.bo != kboCur || alf.cbEntry <= 0 || alf.ivMac < 0 ||
|
|
cb != cbT + CbFromCbit(alf.ivMac) ||
|
|
alf.cvFree >= LwMax(1, alf.ivMac))
|
|
{
|
|
LBug:
|
|
Warn("file corrupt or not an AL");
|
|
goto LFail;
|
|
}
|
|
|
|
_cbEntry = alf.cbEntry;
|
|
_ivMac = alf.ivMac;
|
|
_cvFree = alf.cvFree;
|
|
fRet = _FReadData(pblck, cbT, cb - cbT, size(alf));
|
|
|
|
LFail:
|
|
TrashVarIf(!fRet, pbo);
|
|
TrashVarIf(!fRet, posk);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete all entries in the AL.
|
|
***************************************************************************/
|
|
void AL::DeleteAll(void)
|
|
{
|
|
_ivMac = 0;
|
|
_cvFree = 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Returns whether the given element of the allocated list is free.
|
|
***************************************************************************/
|
|
bool AL::FFree(long iv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
return (iv < _ivMac) && !(*_Qgrfbit(iv) & Fbit(iv));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Make sure there is room for at least cvAdd additional entries. If
|
|
fgrpShrink is set, will try to shrink the list if it has more than
|
|
cvAdd available entries.
|
|
***************************************************************************/
|
|
bool AL::FEnsureSpace(long cvAdd, ulong grfgrp)
|
|
{
|
|
AssertIn(cvAdd, 0, kcbMax);
|
|
AssertThis(0);
|
|
|
|
// limit the size of the list
|
|
cvAdd = LwMax(0, cvAdd - _cvFree);
|
|
if (cvAdd > kcbMax / _cbEntry - _ivMac)
|
|
{
|
|
Bug("who's trying to allocate a list this big?");
|
|
return fFalse;
|
|
}
|
|
|
|
return _FEnsureSizes(LwMul(cvAdd + _ivMac, _cbEntry),
|
|
CbFromCbit(cvAdd + _ivMac), grfgrp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Add an element to the list.
|
|
***************************************************************************/
|
|
bool AL::FAdd(void *pv, long *piv)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPvCb(pv, _cbEntry);
|
|
AssertNilOrVarMem(piv);
|
|
|
|
long iv;
|
|
|
|
if (_cvFree > 0)
|
|
{
|
|
/* find the first free one */
|
|
byte grfbit;
|
|
byte *qgrfbit, *qrgb;
|
|
|
|
for (qgrfbit = qrgb = _Qgrfbit(0); *qgrfbit == 0xFF; qgrfbit++)
|
|
;
|
|
iv = (qgrfbit - qrgb) * 8;
|
|
for (grfbit = *qgrfbit; grfbit & 1; iv++, grfbit >>= 1)
|
|
;
|
|
_cvFree--;
|
|
}
|
|
else
|
|
{
|
|
if (!FEnsureSpace(1, fgrpNil))
|
|
{
|
|
TrashVar(piv);
|
|
return fFalse;
|
|
}
|
|
iv = _ivMac++;
|
|
}
|
|
AssertIn(iv, 0, _ivMac);
|
|
|
|
/* mark the item used */
|
|
*_Qgrfbit(iv) |= Fbit(iv);
|
|
Assert(!FFree(iv), "why is this marked free?");
|
|
Put(iv, pv);
|
|
if (piv != pvNil)
|
|
*piv = iv;
|
|
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete element iv from an allocated list.
|
|
***************************************************************************/
|
|
void AL::Delete(long iv)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "already free!");
|
|
|
|
// trash the thing
|
|
TrashPvCb(QvGet(iv), _cbEntry);
|
|
|
|
*_Qgrfbit(iv) &= ~Fbit(iv);
|
|
_cvFree++;
|
|
|
|
if (iv != _ivMac - 1)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
return;
|
|
}
|
|
|
|
// the last element was deleted, find the new _ivMac
|
|
if (_ivMac <= _cvFree)
|
|
{
|
|
// none left, just nuke everything
|
|
_ivMac = _cvFree = 0;
|
|
}
|
|
else
|
|
{
|
|
// find the new _ivMac
|
|
byte fbit;
|
|
byte *qgrfbit = _Qgrfbit(iv);
|
|
|
|
while (iv >= 0)
|
|
{
|
|
fbit = Fbit(iv);
|
|
if ((*qgrfbit & ((fbit << 1) - 1)) == 0) // check all bits from fbit on down
|
|
{
|
|
iv = (iv & ~0x0007L) - 1;
|
|
qgrfbit--;
|
|
}
|
|
else if (!(*qgrfbit & fbit)) // check for the ith bit
|
|
iv--;
|
|
else
|
|
break;
|
|
}
|
|
iv++;
|
|
Assert(_cvFree >= _ivMac - iv, "everything is free!?");
|
|
_cvFree -= _ivMac - iv;
|
|
_ivMac = iv;
|
|
}
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Check the validity of an allocated list.
|
|
***************************************************************************/
|
|
void AL::AssertValid(ulong grfobj)
|
|
{
|
|
long cT, iv;
|
|
|
|
AL_PAR::AssertValid(0);
|
|
Assert(_Cb2() >= CbFromCbit(_ivMac), "flag area too small");
|
|
if (grfobj & fobjAssertFull)
|
|
{
|
|
for (cT = 0, iv = _ivMac; iv--; )
|
|
{
|
|
if ((*_Qgrfbit(iv) & Fbit(iv)) == 0)
|
|
cT++;
|
|
}
|
|
Assert(cT == _cvFree, "_cvFree is wrong");
|
|
}
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for GGB class.
|
|
***************************************************************************/
|
|
GGB::GGB(long cbFixed, bool fAllowFree)
|
|
{
|
|
AssertIn(cbFixed, 0, kcbMax);
|
|
_clocFree = fAllowFree ? 0 : cvNil;
|
|
_cbFixed = cbFixed;
|
|
|
|
// use some reasonable values for _cbMinGrow* - code can always set
|
|
// set these to something else
|
|
_cbMinGrow1 = LwMin(1024, 16 * cbFixed);
|
|
_cbMinGrow2 = 16 * size(LOC);
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate the group.
|
|
***************************************************************************/
|
|
bool GGB::_FDup(PGGB pggbDst)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pggbDst, fobjAssertFull);
|
|
Assert(_cbFixed == pggbDst->_cbFixed,
|
|
"why do these have different sized fixed portions?");
|
|
|
|
if (!GGB_PAR::_FDup(pggbDst, _bvMac, LwMul(_ivMac, size(LOC))))
|
|
return fFalse;
|
|
|
|
pggbDst->_bvMac = _bvMac;
|
|
pggbDst->_clocFree = _clocFree;
|
|
pggbDst->_cbFixed = _cbFixed;
|
|
AssertPo(pggbDst, fobjAssertFull);
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
//group on file
|
|
struct GGF
|
|
{
|
|
short bo;
|
|
short osk;
|
|
long ilocMac;
|
|
long bvMac;
|
|
long clocFree;
|
|
long cbFixed;
|
|
};
|
|
const BOM kbomGgf = 0x5FF00000L;
|
|
|
|
|
|
/***************************************************************************
|
|
Return the amount of space on file needed for the group.
|
|
***************************************************************************/
|
|
long GGB::CbOnFile(void)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
return size(GGF) + LwMul(_ivMac, size(LOC)) + _bvMac;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the group to disk. The client must ensure that the data in the
|
|
GGB has the correct byte order (as specified by the bo).
|
|
***************************************************************************/
|
|
bool GGB::FWrite(PBLCK pblck, short bo, short osk)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pblck, 0);
|
|
Assert(kboCur == bo || kboOther == bo, "bad bo");
|
|
AssertOsk(osk);
|
|
|
|
GGF ggf;
|
|
bool fRet;
|
|
|
|
ggf.bo = kboCur;
|
|
ggf.osk = osk;
|
|
ggf.ilocMac = _ivMac;
|
|
ggf.bvMac = _bvMac;
|
|
ggf.clocFree = _clocFree;
|
|
ggf.cbFixed = _cbFixed;
|
|
AssertBomRglw(kbomLoc, size(LOC));
|
|
if (kboOther == bo)
|
|
{
|
|
//swap the stuff
|
|
SwapBytesBom(&ggf, kbomGgf);
|
|
Assert(ggf.bo == bo, "wrong bo");
|
|
Assert(ggf.osk == osk, "osk not invariant under byte swapping");
|
|
SwapBytesRglw(_Qb2(0), LwMulDiv(_ivMac, size(LOC), size(long)));
|
|
}
|
|
fRet = _FWrite(pblck, &ggf, size(ggf), _bvMac, LwMul(_ivMac, size(LOC)));
|
|
if (kboOther == bo)
|
|
{
|
|
//swap the rgloc back
|
|
SwapBytesRglw(_Qb2(0), LwMulDiv(_ivMac, size(LOC), size(long)));
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read group data from disk.
|
|
***************************************************************************/
|
|
bool GGB::_FRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
GGF ggf;
|
|
long cbT;
|
|
short bo;
|
|
long cb;
|
|
bool fRet = fFalse;
|
|
|
|
if (!pblck->FUnpackData())
|
|
goto LFail;
|
|
|
|
cb = pblck->Cb();
|
|
if (cb < size(ggf))
|
|
goto LBug;
|
|
|
|
if (!pblck->FReadRgb(&ggf, size(ggf), 0))
|
|
goto LFail;
|
|
|
|
if (pbo != pvNil)
|
|
*pbo = ggf.bo;
|
|
if (posk != pvNil)
|
|
*posk = ggf.osk;
|
|
|
|
if ((bo = ggf.bo) == kboOther)
|
|
SwapBytesBom(&ggf, kbomGgf);
|
|
|
|
cb -= size(ggf);
|
|
cbT = ggf.ilocMac * size(LOC);
|
|
if (ggf.bo != kboCur || ggf.bvMac < 0 || ggf.ilocMac < 0 ||
|
|
cb != cbT + ggf.bvMac ||
|
|
ggf.cbFixed < 0 || ggf.cbFixed >= kcbMax ||
|
|
(ggf.clocFree == cvNil) != (_clocFree == cvNil) ||
|
|
ggf.clocFree != cvNil && (ggf.clocFree < 0 || ggf.clocFree >= ggf.ilocMac))
|
|
{
|
|
LBug:
|
|
Warn("file corrupt or not a GGB");
|
|
goto LFail;
|
|
}
|
|
|
|
_ivMac = ggf.ilocMac;
|
|
_bvMac = ggf.bvMac;
|
|
_clocFree = ggf.clocFree;
|
|
_cbFixed = ggf.cbFixed;
|
|
fRet = _FReadData(pblck, cb - cbT, cbT, size(ggf));
|
|
AssertBomRglw(kbomLoc, size(LOC));
|
|
if (bo == kboOther && fRet)
|
|
{
|
|
// adjust the byte order on the loc's.
|
|
SwapBytesRglw(_Qb2(0), LwMulDiv(_ivMac, size(LOC), size(long)));
|
|
}
|
|
|
|
LFail:
|
|
TrashVarIf(!fRet, pbo);
|
|
TrashVarIf(!fRet, posk);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Returns true iff the loc.bv is nil or iloc is out of range.
|
|
***************************************************************************/
|
|
bool GGB::FFree(long iv)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertIn(iv, 0, kcbMax);
|
|
LOC *qloc;
|
|
|
|
if (!FIn(iv, 0, _ivMac))
|
|
return fTrue;
|
|
qloc = _Qloc(iv);
|
|
Assert(FIn(qloc->bv, 0, _bvMac) &&
|
|
FIn(qloc->cb, LwMax(_cbFixed, 1), _bvMac - qloc->bv + 1) ||
|
|
0 == qloc->cb && (0 == qloc->bv || bvNil == qloc->bv),
|
|
"bad loc");
|
|
return bvNil == qloc->bv;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Ensures that there is room to add at least cvAdd new entries with
|
|
a total of cbAdd bytes (among the variable parts of the elements).
|
|
If there is more than enough room and fgrpShrink is passed, the GST
|
|
will shrink.
|
|
***************************************************************************/
|
|
bool GGB::FEnsureSpace(long cvAdd, long cbAdd, ulong grfgrp)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cvAdd, 0, kcbMax);
|
|
AssertIn(cbAdd, 0, kcbMax);
|
|
|
|
long clocAdd;
|
|
|
|
if (cvNil == _clocFree)
|
|
clocAdd = cvAdd;
|
|
else
|
|
clocAdd = LwMax(0, cvAdd - _clocFree);
|
|
|
|
//we waste at most (size(long) - 1) bytes per element
|
|
if (clocAdd > kcbMax / size(LOC) - _ivMac ||
|
|
cvAdd > (kcbMax / (_cbFixed + size(long) - 1)) - _bvMac ||
|
|
cbAdd > kcbMax - _bvMac - cvAdd * (_cbFixed + size(long) - 1))
|
|
{
|
|
Bug("why is this group growing so large?");
|
|
return fFalse;
|
|
}
|
|
|
|
return _FEnsureSizes(_bvMac + cbAdd + LwMul(cvAdd, _cbFixed + size(long) - 1),
|
|
LwMul(_ivMac + clocAdd, size(LOC)), grfgrp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the minimum that a GGB should grow by.
|
|
***************************************************************************/
|
|
void GGB::SetMinGrow(long cvAdd, long cbAdd)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cvAdd, 0, kcbMax);
|
|
AssertIn(cbAdd, 0, kcbMax);
|
|
|
|
_cbMinGrow1 = CbRoundToLong(cbAdd + LwMul(cvAdd, _cbFixed + size(long) - 1));
|
|
_cbMinGrow2 = LwMul(cvAdd, size(LOC));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Private api to remove a block of bytes.
|
|
***************************************************************************/
|
|
void GGB::_RemoveRgb(long bv, long cb)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertIn(bv, 0, _bvMac);
|
|
AssertIn(cb, 1, _bvMac - bv + 1);
|
|
Assert(cb == CbRoundToLong(cb), "cb not divisible by size(long)");
|
|
byte *qb;
|
|
|
|
if (bv + cb < _bvMac)
|
|
{
|
|
qb = _Qb1(bv);
|
|
BltPb(qb + cb, qb, _bvMac - bv - cb);
|
|
_AdjustLocs(bv + 1, _bvMac + 1, -cb);
|
|
}
|
|
else
|
|
_bvMac -= cb;
|
|
TrashPvCb(_Qb1(_bvMac), cb);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Private api to remove a block of bytes.
|
|
***************************************************************************/
|
|
void GGB::_AdjustLocs(long bvMin, long bvLim, long dcb)
|
|
{
|
|
AssertBaseThis(0);
|
|
AssertIn(bvMin, 0, _bvMac + 2);
|
|
AssertIn(bvLim, bvMin, _bvMac + 2);
|
|
AssertIn(dcb, -_bvMac, kcbMax);
|
|
Assert((dcb % size(long)) == 0, "dcb not divisible by size(long)");
|
|
long cloc;
|
|
LOC *qloc;
|
|
|
|
if (FIn(_bvMac, bvMin, bvLim))
|
|
_bvMac += dcb;
|
|
for (qloc = _Qloc(0), cloc = _ivMac; cloc > 0; cloc--, qloc++)
|
|
{
|
|
if (bvNil == qloc->bv)
|
|
continue;
|
|
if (FIn(qloc->bv, bvMin, bvLim))
|
|
qloc->bv += dcb;
|
|
AssertIn(qloc->bv, 0, _bvMac);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Returns a volative pointer the the fixed sized data in the element.
|
|
If pcbVar is not nil, fills *pcbVar with the size of the variable part.
|
|
***************************************************************************/
|
|
void *GGB::QvFixedGet(long iv, long *pcbVar)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(_cbFixed, 1, kcbMax);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertNilOrVarMem(pcbVar);
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
if (pcbVar != pvNil)
|
|
*pcbVar = loc.cb - _cbFixed;
|
|
AssertIn(loc.cb, _cbFixed, _bvMac - loc.bv + 1);
|
|
return _Qb1(loc.bv);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Lock the data and return a pointer to the fixed sized data.
|
|
***************************************************************************/
|
|
void *GGB::PvFixedLock(long iv, long *pcbVar)
|
|
{
|
|
AssertThis(0);
|
|
Lock();
|
|
return QvFixedGet(iv, pcbVar);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get the fixed sized data for the element.
|
|
***************************************************************************/
|
|
void GGB::GetFixed(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(_cbFixed, 1, kcbMax);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertPvCb(pv, _cbFixed);
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
AssertIn(loc.cb, _cbFixed, _bvMac - loc.bv + 1);
|
|
CopyPb(_Qb1(loc.bv), pv, _cbFixed);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Put the fixed sized data for the element.
|
|
***************************************************************************/
|
|
void GGB::PutFixed(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(_cbFixed, 1, kcbMax);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertPvCb(pv, _cbFixed);
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
AssertIn(loc.cb, _cbFixed, _bvMac - loc.bv + 1);
|
|
CopyPb(pv, _Qb1(loc.bv), _cbFixed);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the length of the variable part of the iv'th element.
|
|
***************************************************************************/
|
|
long GGB::Cb(long iv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
|
|
return _Qloc(iv)->cb - _cbFixed;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return a volatile pointer to the variable part of the iv'th element.
|
|
If pcb is not nil, sets *pcb to the length of the (variable part of the)
|
|
item.
|
|
***************************************************************************/
|
|
void *GGB::QvGet(long iv, long *pcb)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertNilOrVarMem(pcb);
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
if (pcb != pvNil)
|
|
*pcb = loc.cb - _cbFixed;
|
|
AssertIn(loc.cb, _cbFixed, _bvMac - loc.bv + 1);
|
|
return _Qb1(loc.bv + _cbFixed);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Lock the data and return a pointer to the (variable part of the) iv'th
|
|
item. If pcb is not nil, sets *pcb to the length of the (variable part
|
|
of the) item.
|
|
***************************************************************************/
|
|
void *GGB::PvLock(long iv, long *pcb)
|
|
{
|
|
AssertThis(0);
|
|
Lock();
|
|
return QvGet(iv, pcb);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Copy the (variable part of the) iv'th element to pv.
|
|
***************************************************************************/
|
|
void GGB::Get(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
AssertPvCb(pv, loc.cb - _cbFixed);
|
|
CopyPb(_Qb1(loc.bv + _cbFixed), pv, loc.cb - _cbFixed);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Copy *pv to the (variable part of the) iv'th element.
|
|
***************************************************************************/
|
|
void GGB::Put(long iv, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
|
|
LOC loc;
|
|
|
|
loc = *_Qloc(iv);
|
|
AssertPvCb(pv, loc.cb - _cbFixed);
|
|
CopyPb(pv, _Qb1(loc.bv + _cbFixed), loc.cb - _cbFixed);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Replace the (variable part of the) iv'th element with the stuff in pv
|
|
(cb bytes worth). pv may be nil (effectively resizing the block).
|
|
***************************************************************************/
|
|
bool GGB::FPut(long iv, long cb, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertIn(cb, 0, kcbMax);
|
|
|
|
long cbCur = Cb(iv);
|
|
|
|
if (cb > cbCur)
|
|
{
|
|
if (!FInsertRgb(iv, cbCur, cb - cbCur, pvNil))
|
|
return fFalse;
|
|
}
|
|
else if (cb < cbCur)
|
|
DeleteRgb(iv, cb, cbCur - cb);
|
|
|
|
if (pv != pvNil && cb > 0)
|
|
{
|
|
AssertPvCb(pv, cb);
|
|
CopyPb(pv, QvGet(iv), cb);
|
|
}
|
|
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get a portion of the element.
|
|
***************************************************************************/
|
|
void GGB::GetRgb(long iv, long bv, long cb, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertPvCb(pv, cb);
|
|
|
|
LOC loc;
|
|
|
|
bv += _cbFixed;
|
|
loc = *_Qloc(iv);
|
|
AssertIn(bv, _cbFixed, loc.cb);
|
|
AssertIn(cb, 1, loc.cb - bv + 1);
|
|
|
|
CopyPb(_Qb1(loc.bv + bv), pv, cb);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Put a portion of the element.
|
|
***************************************************************************/
|
|
void GGB::PutRgb(long iv, long bv, long cb, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertPvCb(pv, cb);
|
|
|
|
LOC loc;
|
|
|
|
bv += _cbFixed;
|
|
loc = *_Qloc(iv);
|
|
AssertIn(bv, _cbFixed, loc.cb);
|
|
AssertIn(cb, 1, loc.cb - bv + 1);
|
|
|
|
CopyPb(pv, _Qb1(loc.bv + bv), cb);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Remove a portion of element iv (can't be all of it).
|
|
***************************************************************************/
|
|
void GGB::DeleteRgb(long iv, long bv, long cb)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
|
|
LOC loc;
|
|
LOC *qloc;
|
|
long cbDel;
|
|
byte *qb;
|
|
|
|
bv += _cbFixed;
|
|
loc = *_Qloc(iv);
|
|
AssertIn(bv, _cbFixed, loc.cb);
|
|
AssertIn(cb, 1, loc.cb - bv + 1);
|
|
|
|
if (bv + cb < loc.cb)
|
|
{
|
|
// shift usable stuff down
|
|
qb = _Qb1(loc.bv + bv);
|
|
BltPb(qb + cb, qb, loc.cb - bv - cb);
|
|
}
|
|
|
|
// determine the number of bytes to nuke
|
|
cbDel = CbRoundToLong(loc.cb) - CbRoundToLong(loc.cb - cb);
|
|
if (cbDel > 0)
|
|
_RemoveRgb(loc.bv + loc.cb - cb, cbDel);
|
|
|
|
qloc = _Qloc(iv);
|
|
if (0 == (qloc->cb -= cb))
|
|
{
|
|
Assert(_cbFixed == 0, "oops!");
|
|
qloc->bv = 0; //empty element
|
|
}
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Insert cb new bytes at location bv into the iv'th element. pv may
|
|
be nil.
|
|
***************************************************************************/
|
|
bool GGB::FInsertRgb(long iv, long bv, long cb, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "element free!");
|
|
AssertIn(cb, 1, kcbMax);
|
|
|
|
LOC loc;
|
|
long cbAdd;
|
|
byte *qb;
|
|
|
|
bv += _cbFixed;
|
|
loc = *_Qloc(iv);
|
|
AssertIn(bv, _cbFixed, loc.cb + 1);
|
|
if (loc.cb == 0)
|
|
loc.bv = _bvMac;
|
|
|
|
// need to add this many bytes to _bvMac
|
|
cbAdd = CbRoundToLong(loc.cb + cb) - CbRoundToLong(loc.cb);
|
|
if (cbAdd > 0)
|
|
{
|
|
long bvT;
|
|
|
|
if (!_FEnsureSizes(_bvMac + cbAdd, LwMul(_ivMac, size(LOC)), fgrpNil))
|
|
return fFalse;
|
|
|
|
// move later entries back
|
|
bvT = loc.bv + CbRoundToLong(loc.cb);
|
|
if (bvT < _bvMac)
|
|
{
|
|
qb = _Qb1(bvT);
|
|
BltPb(qb, qb + cbAdd, _bvMac - bvT);
|
|
_AdjustLocs(loc.bv + 1, _bvMac + 1, cbAdd);
|
|
}
|
|
else
|
|
_bvMac += cbAdd;
|
|
}
|
|
|
|
// move data within this element
|
|
if (bv < loc.cb)
|
|
{
|
|
qb = _Qb1(loc.bv + bv);
|
|
BltPb(qb, qb + cb, loc.cb - bv);
|
|
}
|
|
|
|
if (pv != pvNil)
|
|
{
|
|
AssertPvCb(pv, cb);
|
|
CopyPb(pv, _Qb1(loc.bv + bv), cb);
|
|
}
|
|
else
|
|
TrashPvCb(_Qb1(loc.bv + bv), cb);
|
|
|
|
//copy the entire loc in case loc.bv got set to _bvMac (if the item was empty)
|
|
loc.cb += cb;
|
|
*_Qloc(iv) = loc;
|
|
AssertThis(0);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move cb bytes from position bvSrc in ivSrc to position bvDst in ivDst.
|
|
This can fail only because of the padding used for each entry (at most
|
|
size(long) additional bytes will need to be allocated).
|
|
***************************************************************************/
|
|
bool GGB::FMoveRgb(long ivSrc, long bvSrc, long ivDst, long bvDst, long cb)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(ivSrc, 0, _ivMac);
|
|
Assert(!FFree(ivSrc), "element free!");
|
|
AssertIn(ivDst, 0, _ivMac);
|
|
Assert(!FFree(ivDst), "element free!");
|
|
AssertIn(bvSrc, 0, Cb(ivSrc) + 1);
|
|
AssertIn(cb, 0, Cb(ivSrc) + 1 - bvSrc);
|
|
AssertIn(bvDst, 0, Cb(ivDst) + 1);
|
|
|
|
LOC *qloc;
|
|
LOC locSrc, locDst;
|
|
long cbMove, cbT;
|
|
|
|
locSrc = *_Qloc(ivSrc);
|
|
locDst = *_Qloc(ivDst);
|
|
|
|
// determine the number of bytes to resize by
|
|
cbT = (CbRoundToLong(locDst.cb + cb) - CbRoundToLong(locDst.cb)) -
|
|
(CbRoundToLong(locSrc.cb) - CbRoundToLong(locSrc.cb - cb));
|
|
if (cbT > 0)
|
|
{
|
|
Assert(cb % size(long) != 0,
|
|
"why are we here when cb is a multiple of size(long)?");
|
|
if (!_FEnsureSizes(_bvMac + cbT, LwMul(_ivMac, size(LOC)), fgrpNil))
|
|
return fFalse;
|
|
}
|
|
|
|
//move most of the bytes
|
|
cbMove = LwRoundToward(cb, size(long));
|
|
AssertIn(cb, cbMove, cbMove + size(long));
|
|
if (cbMove > 0)
|
|
{
|
|
long bv1 = locSrc.bv + bvSrc + _cbFixed;
|
|
long bv2 = locDst.bv + bvDst + _cbFixed;
|
|
|
|
qloc = _Qloc(ivSrc);
|
|
if (0 == (qloc->cb -= cbMove))
|
|
{
|
|
Assert(_cbFixed == 0, "what?");
|
|
qloc->bv = 0;
|
|
}
|
|
qloc = _Qloc(ivDst);
|
|
if (qloc->cb == 0)
|
|
{
|
|
Assert(_cbFixed == 0, "what?");
|
|
bv2 = qloc->bv = _bvMac;
|
|
}
|
|
qloc->cb += cbMove;
|
|
if (bv1 < bv2)
|
|
{
|
|
SwapBlocks(_Qb1(bv1), cbMove, bv2 - bv1 - cbMove);
|
|
_AdjustLocs(locSrc.bv + 1, locDst.bv + 1, -cbMove);
|
|
}
|
|
else
|
|
{
|
|
SwapBlocks(_Qb1(bv2), bv1 - bv2, cbMove);
|
|
_AdjustLocs(locDst.bv + 1, locSrc.bv + 1, cbMove);
|
|
}
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
//move the last few bytes
|
|
if (cb > cbMove)
|
|
{
|
|
byte rgb[size(long)];
|
|
|
|
GetRgb(ivSrc, bvSrc, cb - cbMove, rgb);
|
|
DeleteRgb(ivSrc, bvSrc, cb - cbMove);
|
|
AssertDo(FInsertRgb(ivDst, bvDst + cbMove, cb - cbMove, rgb),
|
|
"logic error caused failure");
|
|
}
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Append the variable data of ivSrc to ivDst, and delete ivSrc.
|
|
|
|
NOTE: this is kind of goofy. The only time FMoveRgb could possibly
|
|
fail if we just do the naive thing (FMoveRgb the entire var data,
|
|
then delete the source element) is if _cbFixed is not a multiple
|
|
of size(long).
|
|
***************************************************************************/
|
|
void GGB::Merge(long ivSrc, long ivDst)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(ivSrc, 0, _ivMac);
|
|
Assert(!FFree(ivSrc), "element free!");
|
|
AssertIn(ivDst, 0, _ivMac);
|
|
Assert(!FFree(ivDst), "element free!");
|
|
Assert(ivSrc != ivDst, "can't merge an element with itself!");
|
|
long cb, cbMove, bv;
|
|
byte rgb[size(long)];
|
|
|
|
cb = Cb(ivSrc);
|
|
cbMove = LwRoundToward(cb, size(long));
|
|
if (cb > cbMove)
|
|
GetRgb(ivSrc, cbMove, cb - cbMove, rgb); //get the tail bytes
|
|
|
|
bv = Cb(ivDst);
|
|
if (cbMove > 0)
|
|
{
|
|
//move the main section
|
|
AssertDo(FMoveRgb(ivSrc, 0, ivDst, bv, cbMove), "why did FMoveRgb fail?");
|
|
}
|
|
|
|
//delete the source item
|
|
Delete(ivSrc);
|
|
if (ivSrc < ivDst)
|
|
ivDst--;
|
|
|
|
if (cb > cbMove)
|
|
{
|
|
//insert the remaining few bytes - there should already be room
|
|
//for these
|
|
AssertDo(FInsertRgb(ivDst, bv + cbMove, cb - cbMove, rgb),
|
|
"why did FInsertRgb fail?");
|
|
}
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a group.
|
|
***************************************************************************/
|
|
void GGB::AssertValid(ulong grfobj)
|
|
{
|
|
LOC loc;
|
|
long iloc;
|
|
long cbTot, clocFree;
|
|
|
|
GGB_PAR::AssertValid(grfobj);
|
|
AssertIn(_ivMac, 0, kcbMax);
|
|
AssertIn(_bvMac, 0, kcbMax);
|
|
Assert(_Cb1() >= _bvMac, "group area too small");
|
|
Assert(_Cb2() >= LwMul(_ivMac, size(LOC)), "rgloc area too small");
|
|
Assert(_clocFree == cvNil || _clocFree == 0 ||
|
|
_clocFree > 0 && _clocFree < _ivMac, "_clocFree is wrong");
|
|
AssertIn(_cbFixed, 0, kcbMax);
|
|
|
|
if (grfobj & fobjAssertFull)
|
|
{
|
|
for (clocFree = cbTot = iloc = 0; iloc < _ivMac; iloc++)
|
|
{
|
|
loc = *_Qloc(iloc);
|
|
if (bvNil == loc.bv)
|
|
{
|
|
Assert(iloc < _ivMac - 1, "Last element free");
|
|
Assert(loc.cb == 0, "bad cb in free loc");
|
|
clocFree++;
|
|
continue;
|
|
}
|
|
AssertIn(loc.cb, _cbFixed, _bvMac + 1);
|
|
AssertIn(loc.bv, 0, _bvMac);
|
|
Assert(loc.cb > 0 || loc.bv == 0, "zero sized item doesn't have zero bv");
|
|
loc.cb = CbRoundToLong(loc.cb);
|
|
Assert(loc.bv + loc.cb <= _bvMac, "loc extends past _bvMac");
|
|
cbTot += loc.cb;
|
|
}
|
|
Assert(cbTot == _bvMac, "group wrong size");
|
|
Assert(clocFree == _clocFree || _clocFree == cvNil && clocFree == 0,
|
|
"bad _clocFree");
|
|
}
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new group with room for at least cvInit elements containing
|
|
at least cbInit bytes worth of (total) space.
|
|
***************************************************************************/
|
|
PGG GG::PggNew(long cbFixed, long cvInit, long cbInit)
|
|
{
|
|
AssertIn(cbFixed, 0, kcbMax);
|
|
AssertIn(cvInit, 0, kcbMax);
|
|
AssertIn(cbInit, 0, kcbMax);
|
|
|
|
PGG pgg;
|
|
|
|
if ((pgg = NewObj GG(cbFixed)) == pvNil)
|
|
return pvNil;
|
|
if ((cvInit > 0 || cbInit > 0) &&
|
|
!pgg->FEnsureSpace(cvInit, cbInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&pgg);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgg, fobjAssertFull);
|
|
return pgg;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a group from a block and return it.
|
|
***************************************************************************/
|
|
PGG GG::PggRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
PGG pgg;
|
|
|
|
if ((pgg = NewObj GG(0)) == pvNil)
|
|
goto LFail;
|
|
if (!pgg->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&pgg);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgg, fobjAssertFull);
|
|
return pgg;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a group from file and return it.
|
|
***************************************************************************/
|
|
PGG GG::PggRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck(pfil, fp, cb);
|
|
return PggRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this GG.
|
|
***************************************************************************/
|
|
PGG GG::PggDup(void)
|
|
{
|
|
AssertThis(0);
|
|
PGG pgg;
|
|
|
|
if (pvNil == (pgg = PggNew(_cbFixed)))
|
|
return pvNil;
|
|
|
|
if (!_FDup(pgg))
|
|
ReleasePpo(&pgg);
|
|
|
|
AssertNilOrPo(pgg, 0);
|
|
return pgg;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Insert an element into the group.
|
|
***************************************************************************/
|
|
bool GG::FInsert(long iv, long cb, void *pv, void *pvFixed)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(cb, 0, kcbMax);
|
|
AssertIn(iv, 0, _ivMac + 1);
|
|
|
|
byte *qb;
|
|
LOC loc;
|
|
LOC *qloc;
|
|
|
|
cb += _cbFixed;
|
|
loc.cb = cb;
|
|
loc.bv = cb == 0 ? 0 : _bvMac;
|
|
cb = CbRoundToLong(cb);
|
|
|
|
if (!_FEnsureSizes(_bvMac + cb, LwMul(_ivMac + 1, size(LOC)), fgrpNil))
|
|
return fFalse;
|
|
|
|
// make room for the entry
|
|
qloc = _Qloc(iv);
|
|
if (iv < _ivMac)
|
|
BltPb(qloc, qloc + 1, LwMul(_ivMac - iv, size(LOC)));
|
|
*qloc = loc;
|
|
|
|
if (pvNil != pv && cb > 0)
|
|
{
|
|
AssertPvCb(pv, cb - _cbFixed);
|
|
qb = _Qb1(loc.bv);
|
|
TrashPvCb(qb, _cbFixed);
|
|
CopyPb(pv, qb + _cbFixed, loc.cb - _cbFixed);
|
|
TrashPvCb(qb + loc.cb, cb - loc.cb);
|
|
}
|
|
else
|
|
TrashPvCb(_Qb1(loc.bv), cb);
|
|
|
|
if (pvNil != pvFixed)
|
|
{
|
|
Assert(_cbFixed > 0, "why is pvFixed not nil?");
|
|
AssertPvCb(pvFixed, _cbFixed);
|
|
qb = _Qb1(loc.bv);
|
|
CopyPb(pvFixed, qb, _cbFixed);
|
|
}
|
|
|
|
_bvMac += cb;
|
|
_ivMac++;
|
|
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Takes cv entries from pggSrc at ivSrc and inserts (a copy of) them into
|
|
this GG at ivDst.
|
|
***************************************************************************/
|
|
bool GG::FCopyEntries(PGG pggSrc, long ivSrc, long ivDst, long cv)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pggSrc, 0);
|
|
AssertIn(cv, 0, pggSrc->IvMac() + 1);
|
|
AssertIn(ivSrc, 0, pggSrc->IvMac() + 1 - cv);
|
|
AssertIn(ivDst, 0, _ivMac + 1);
|
|
long cb, cbFixed, iv, ivLim;
|
|
LOC loc;
|
|
byte *pb;
|
|
|
|
if ((cbFixed = pggSrc->CbFixed()) != CbFixed())
|
|
{
|
|
Bug("Groups have different fixed sizes");
|
|
return fFalse;
|
|
}
|
|
if (pggSrc == this)
|
|
{
|
|
Bug("Cant insert from same group");
|
|
return fFalse;
|
|
}
|
|
|
|
cb = 0;
|
|
for (iv = ivSrc, ivLim = ivSrc + cv; iv < ivLim; iv++)
|
|
cb += pggSrc->Cb(iv);
|
|
|
|
if (!FEnsureSpace(cv, cb))
|
|
return fFalse;
|
|
|
|
pggSrc->Lock();
|
|
for (iv = ivSrc; iv < ivLim; iv++)
|
|
{
|
|
loc = *pggSrc->_Qloc(iv);
|
|
pb = pggSrc->_Qb1(loc.bv);
|
|
AssertDo(FInsert(ivDst + iv - ivSrc, loc.cb - cbFixed, pb + cbFixed,
|
|
cbFixed == 0 ? pvNil : pb), 0);
|
|
}
|
|
pggSrc->Unlock();
|
|
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Append an element to the group.
|
|
***************************************************************************/
|
|
bool GG::FAdd(long cb, long *piv, void *pv, void *pvFixed)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrVarMem(piv);
|
|
|
|
if (piv != pvNil)
|
|
*piv = _ivMac;
|
|
if (!FInsert(_ivMac, cb, pv, pvFixed))
|
|
{
|
|
TrashVar(piv);
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete an element from the group.
|
|
***************************************************************************/
|
|
void GG::Delete(long iv)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(iv, 0, _ivMac);
|
|
LOC *qloc;
|
|
LOC loc;
|
|
|
|
qloc = _Qloc(iv);
|
|
loc = *qloc;
|
|
if (iv < --_ivMac)
|
|
BltPb(qloc + 1, qloc, LwMul(_ivMac - iv, size(LOC)));
|
|
TrashPvCb(_Qloc(_ivMac), size(LOC));
|
|
if (loc.cb > 0)
|
|
_RemoveRgb(loc.bv, CbRoundToLong(loc.cb));
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move the entry at ivSrc to be immediately before the element that is
|
|
currently at ivTarget. If ivTarget > ivSrc, the entry actually ends
|
|
up at (ivTarget - 1) and the entry at ivTarget doesn't move. If
|
|
ivTarget < ivSrc, the entry ends up at ivTarget and the entry at
|
|
ivTarget moves to (ivTarget + 1). Everything in between is shifted
|
|
appropriately. ivTarget is allowed to be equal to IvMac().
|
|
***************************************************************************/
|
|
void GG::Move(long ivSrc, long ivTarget)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(ivSrc, 0, _ivMac);
|
|
AssertIn(ivTarget, 0, _ivMac + 1);
|
|
|
|
MoveElement(_Qloc(0), size(LOC), ivSrc, ivTarget);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Swap two elements in a GG.
|
|
***************************************************************************/
|
|
void GG::Swap(long iv1, long iv2)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(iv1, 0, _ivMac);
|
|
AssertIn(iv2, 0, _ivMac);
|
|
|
|
SwapPb(_Qloc(iv1), _Qloc(iv2), size(LOC));
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a group.
|
|
***************************************************************************/
|
|
void GG::AssertValid(ulong grfobj)
|
|
{
|
|
GG_PAR::AssertValid(grfobj);
|
|
AssertVar(_clocFree == cvNil, "bad _clocFree in GG", &_clocFree);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new allocated group with romm for at least cvInit elements
|
|
containing at least cbInit bytes worth of (total) space.
|
|
***************************************************************************/
|
|
PAG AG::PagNew(long cbFixed, long cvInit, long cbInit)
|
|
{
|
|
AssertIn(cbFixed, 0, kcbMax);
|
|
AssertIn(cvInit, 0, kcbMax);
|
|
AssertIn(cbInit, 0, kcbMax);
|
|
|
|
PAG pag;
|
|
|
|
if ((pag = NewObj AG(cbFixed)) == pvNil)
|
|
return pvNil;
|
|
if ((cvInit > 0 || cbInit > 0) &&
|
|
!pag->FEnsureSpace(cvInit, cbInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&pag);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pag, fobjAssertFull);
|
|
return pag;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated group from a block and return it.
|
|
***************************************************************************/
|
|
PAG AG::PagRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
PAG pag;
|
|
|
|
if ((pag = NewObj AG(0)) == pvNil)
|
|
goto LFail;
|
|
if (!pag->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&pag);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pag, fobjAssertFull);
|
|
return pag;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated group from file and return it.
|
|
***************************************************************************/
|
|
PAG AG::PagRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck(pfil, fp, cb);
|
|
return PagRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this AG.
|
|
***************************************************************************/
|
|
PAG AG::PagDup(void)
|
|
{
|
|
AssertThis(0);
|
|
PAG pag;
|
|
|
|
if (pvNil == (pag = PagNew(_cbFixed)))
|
|
return pvNil;
|
|
|
|
if (!_FDup(pag))
|
|
ReleasePpo(&pag);
|
|
|
|
AssertNilOrPo(pag, 0);
|
|
return pag;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Add an element to the allocated group.
|
|
***************************************************************************/
|
|
bool AG::FAdd(long cb, long *piv, void *pv, void *pvFixed)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(cb, 0, kcbMax);
|
|
AssertNilOrVarMem(piv);
|
|
|
|
long iloc;
|
|
byte *qb;
|
|
LOC loc;
|
|
LOC *qloc;
|
|
|
|
cb += _cbFixed;
|
|
AssertIn(cb, 0, kcbMax);
|
|
|
|
if (0 < _clocFree)
|
|
{
|
|
//find the first free element
|
|
qloc = _Qloc(0);
|
|
for (iloc = 0; iloc < _ivMac; iloc++, qloc++)
|
|
{
|
|
if (qloc->bv == bvNil)
|
|
break;
|
|
}
|
|
Assert(iloc < _ivMac - 1, "_clocFree wrong");
|
|
_clocFree--;
|
|
}
|
|
else
|
|
iloc = _ivMac;
|
|
if (iloc == _ivMac)
|
|
_ivMac++;
|
|
|
|
loc.cb = cb;
|
|
loc.bv = cb == 0 ? 0 : _bvMac;
|
|
cb = CbRoundToLong(cb);
|
|
|
|
if (!_FEnsureSizes(_bvMac + cb, LwMul(_ivMac, size(LOC)), fgrpNil))
|
|
{
|
|
if (iloc == _ivMac - 1)
|
|
_ivMac--;
|
|
else
|
|
_clocFree++;
|
|
TrashVar(piv);
|
|
return fFalse;
|
|
}
|
|
|
|
// fill in the loc and copy the data
|
|
*_Qloc(iloc) = loc;
|
|
if (pv != pvNil)
|
|
{
|
|
AssertPvCb(pv, cb - _cbFixed);
|
|
qb = _Qb1(loc.bv);
|
|
TrashPvCb(qb , _cbFixed);
|
|
CopyPb(pv, qb + _cbFixed, loc.cb - _cbFixed);
|
|
TrashPvCb(qb + loc.cb, cb - loc.cb);
|
|
}
|
|
else
|
|
TrashPvCb(_Qb1(loc.bv), cb);
|
|
|
|
if (pvNil != pvFixed)
|
|
{
|
|
Assert(_cbFixed > 0, "why is pvFixed not nil?");
|
|
AssertPvCb(pvFixed, _cbFixed);
|
|
qb = _Qb1(loc.bv);
|
|
CopyPb(pvFixed, qb, _cbFixed);
|
|
}
|
|
|
|
_bvMac += cb;
|
|
if (pvNil != piv)
|
|
*piv = iloc;
|
|
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete an element from the group.
|
|
***************************************************************************/
|
|
void AG::Delete(long iv)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(iv, 0, _ivMac);
|
|
Assert(!FFree(iv), "entry already free!");
|
|
|
|
LOC *qloc;
|
|
LOC loc;
|
|
|
|
qloc = _Qloc(iv);
|
|
loc = *qloc;
|
|
|
|
if (iv == _ivMac - 1)
|
|
{
|
|
//move _ivMac back past any free entries on the end
|
|
while (--_ivMac > 0 && (--qloc)->bv == bvNil)
|
|
_clocFree--;
|
|
TrashPvCb(_Qloc(_ivMac), LwMul(iv - _ivMac + 1, size(LOC)));
|
|
}
|
|
else
|
|
{
|
|
qloc->bv = bvNil;
|
|
qloc->cb = 0;
|
|
_clocFree++;
|
|
}
|
|
if (loc.cb > 0)
|
|
_RemoveRgb(loc.bv, CbRoundToLong(loc.cb));
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a group.
|
|
***************************************************************************/
|
|
void AG::AssertValid(ulong grfobj)
|
|
{
|
|
AG_PAR::AssertValid(grfobj);
|
|
AssertIn(_clocFree, 0, LwMax(1, _ivMac));
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|