openclonk/src/object/C4IDList.cpp

533 lines
12 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* At static list of C4IDs */
#include "C4Include.h"
#include "object/C4IDList.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "object/C4Def.h"
#include "object/C4DefList.h"
C4IDListChunk::C4IDListChunk()
{
// prepare list
pNext=nullptr;
}
C4IDListChunk::~C4IDListChunk()
{
// empty the list
Clear();
}
void C4IDListChunk::Clear()
{
// kill all chunks
C4IDListChunk *pChunk=pNext,*pChunk2;
while (pChunk)
{
pChunk2=pChunk->pNext; pChunk->pNext=nullptr;
delete pChunk; pChunk=pChunk2;
}
pNext=nullptr;
}
C4IDList::C4IDList() : C4IDListChunk()
{
Default();
}
C4IDList::C4IDList(const C4IDList &rCopy): C4IDListChunk()
{
Default();
*this = rCopy;
}
C4IDList &C4IDList::operator = (const C4IDList &rCopy)
{
// clear previous list
Clear();
// copy primary
memcpy(this, &rCopy, sizeof(C4IDList));
// copy all chunks
C4IDListChunk *pTo=this;
for (C4IDListChunk *pFrom=rCopy.pNext; pFrom; pFrom=pFrom->pNext)
{
C4IDListChunk *pNew = new C4IDListChunk(*pFrom);
pTo->pNext=pNew; pTo=pNew;
}
// finalize
pTo->pNext=nullptr;
return *this;
}
C4IDList::~C4IDList()
{
// destruction is done in chunk
}
void C4IDList::Clear()
{
// inherited
C4IDListChunk::Clear();
// reset count
Count=0;
}
bool C4IDList::IsClear() const
{
return !Count;
}
C4ID C4IDList::GetID(size_t index, int32_t *ipCount) const
{
// outside list?
if (!Inside<size_t>(index+1,1u,Count)) return C4ID::None;
// get chunk to query
const C4IDListChunk *pQueryChunk=this;
while (index>=C4IDListChunkSize) { pQueryChunk=pQueryChunk->pNext; index-=C4IDListChunkSize; }
// query it
if (ipCount) *ipCount=pQueryChunk->Count[index];
return pQueryChunk->id[index];
}
int32_t C4IDList::GetCount(size_t index) const
{
// outside list?
if (!Inside<size_t>(index+1,1u,Count)) return 0;
// get chunk to query
const C4IDListChunk *pQueryChunk=this;
while (index>=C4IDListChunkSize) { pQueryChunk=pQueryChunk->pNext; index-=C4IDListChunkSize; }
// query it
return pQueryChunk->Count[index];
}
bool C4IDList::SetCount(size_t index, int32_t iCount)
{
// outside list?
if (!Inside<size_t>(index+1,1u,Count)) return false;
// get chunk to set in
C4IDListChunk *pQueryChunk=this;
while (index>=C4IDListChunkSize) { pQueryChunk=pQueryChunk->pNext; index-=C4IDListChunkSize; }
// set it
pQueryChunk->Count[index]=iCount;
// success
return true;
}
int32_t C4IDList::GetIDCount(C4ID c_id, int32_t iZeroDefVal) const
{
// find id
const C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if (pQueryChunk->id[cntl]==c_id)
{
int32_t iCount=pQueryChunk->Count[cntl];
return iCount ? iCount : iZeroDefVal;
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
// none found
return 0;
}
bool C4IDList::SetIDCount(C4ID c_id, int32_t iCount, bool fAddNewID)
{
// find id
C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if (pQueryChunk->id[cntl]==c_id)
{
pQueryChunk->Count[cntl]=iCount; return true;
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
// none found: add new
if (fAddNewID)
{
// if end was reached, add new chunk
if (!pQueryChunk)
{
C4IDListChunk *pLast=this;
while (pLast->pNext) pLast=pLast->pNext;
pQueryChunk=new C4IDListChunk();
pLast->pNext=pQueryChunk;
}
// set id
pQueryChunk->id[cntl]=c_id;
pQueryChunk->Count[cntl]=iCount;
// count id!
++Count;
// success
return true;
}
// failure
return false;
}
int32_t C4IDList::GetNumberOfIDs() const
{
return Count;
}
int32_t C4IDList::GetIndex(C4ID c_id) const
{
// find id in list
const C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if (pQueryChunk->id[cntl]==c_id) return Count-cnt-1;
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
// not found
return -1;
}
bool C4IDList::IncreaseIDCount(C4ID c_id, bool fAddNewID, int32_t IncreaseBy, bool fRemoveEmpty)
{
// find id in list
C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if (pQueryChunk->id[cntl]==c_id)
{
// increase count
pQueryChunk->Count[cntl]+=IncreaseBy;
// check count
if (fRemoveEmpty && !pQueryChunk->Count[cntl]) DeleteItem(Count-cnt-1);
// success
return true;
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
// add desired?
if (!fAddNewID) return true;
// add it
// if end was reached, add new chunk
if (!pQueryChunk)
{
C4IDListChunk *pLast=this;
while (pLast->pNext) pLast=pLast->pNext;
pQueryChunk=new C4IDListChunk();
pLast->pNext=pQueryChunk;
}
// set id
pQueryChunk->id[cntl]=c_id;
pQueryChunk->Count[cntl]=IncreaseBy;
++Count;
// success
return true;
}
// Access by category-sorted index
C4ID C4IDList::GetID(C4DefList &rDefs, int32_t dwCategory, int32_t index, int32_t *ipCount) const
{
int32_t cindex=-1;
C4Def *cDef;
if (ipCount) *ipCount=0;
// find id
const C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if ((dwCategory==C4D_All) || ( (cDef=rDefs.ID2Def(pQueryChunk->id[cntl])) && (cDef->Category & dwCategory) ) )
{
cindex++;
if (cindex==index) { if (ipCount) *ipCount=pQueryChunk->Count[cntl]; return pQueryChunk->id[cntl]; }
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return C4ID::None;
}
int32_t C4IDList::GetCount(C4DefList &rDefs, int32_t dwCategory, int32_t index) const
{
int32_t cindex=-1;
C4Def *cDef;
const C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if ((dwCategory==C4D_All) || ( (cDef=rDefs.ID2Def(pQueryChunk->id[cntl])) && (cDef->Category & dwCategory) ) )
{
cindex++;
if (cindex==index) return pQueryChunk->Count[cntl];
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return 0;
}
bool C4IDList::SetCount(C4DefList &rDefs, int32_t dwCategory, int32_t index, int32_t iCount)
{
int32_t cindex=-1;
C4Def *cDef;
C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if ((dwCategory==C4D_All) || ( (cDef=rDefs.ID2Def(pQueryChunk->id[cntl])) && (cDef->Category & dwCategory) ) )
{
cindex++;
if (cindex==index) { pQueryChunk->Count[cntl]=iCount; return true; }
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return false;
}
int32_t C4IDList::GetNumberOfIDs(C4DefList &rDefs, int32_t dwCategory) const
{
int32_t idnum=0;
C4Def *cDef;
const C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
while (cnt--)
{
if ((dwCategory==C4D_All) || ( (cDef=rDefs.ID2Def(pQueryChunk->id[cntl])) && (cDef->Category & dwCategory) ) )
idnum++;
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return idnum;
}
// IDList merge
bool C4IDList::Add(C4IDList &rList)
{
C4IDListChunk *pQueryChunk=&rList;
size_t cnt=rList.Count,cntl=0;
while (cnt--)
{
IncreaseIDCount(pQueryChunk->id[cntl], true, pQueryChunk->Count[cntl]);
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return true;
}
bool C4IDList::ConsolidateValids(C4DefList &rDefs, int32_t dwCategory)
{
bool fIDsRemoved=false;
C4IDListChunk *pQueryChunk=this;
size_t cnt=Count,cntl=0;
C4Def* pDef;
while (cnt--)
{
// ID does not resolve to a valid def or category is specified and def does not match category
if (!(pDef = rDefs.ID2Def(pQueryChunk->id[cntl])) || (dwCategory && !(pDef->Category & dwCategory)))
{
// delete it
DeleteItem(Count-cnt-1);
// handle this list index again!
--cntl;
// something was done
fIDsRemoved=true;
}
if (++cntl==C4IDListChunkSize)
{
pQueryChunk=pQueryChunk->pNext;
cntl=0;
}
}
return fIDsRemoved;
}
void C4IDList::Draw(C4Facet &cgo, int32_t iSelection,
C4DefList &rDefs, DWORD dwCategory,
bool fCounts, int32_t iAlign) const
{
int32_t sections = cgo.GetSectionCount();
int32_t idnum = GetNumberOfIDs(rDefs,dwCategory);
int32_t firstid = Clamp<int32_t>(iSelection-sections/2,0,std::max<int32_t>(idnum-sections,0));
int32_t idcount;
C4ID c_id;
C4Facet cgo2;
char buf[10];
for (int32_t cnt=0; (cnt<sections) && (c_id=GetID(rDefs,dwCategory,firstid+cnt,&idcount)); cnt++)
{
cgo2 = cgo.TruncateSection(iAlign);
rDefs.Draw(c_id,cgo2,(firstid+cnt==iSelection),0);
sprintf(buf,"%dx",idcount);
if (fCounts) pDraw->TextOut(buf, ::GraphicsResource.FontRegular, 1.0, cgo2.Surface,cgo2.X+cgo2.Wdt-1, cgo2.Y + cgo2.Hgt - 1 - ::GraphicsResource.FontRegular.GetLineHeight(),C4Draw::DEFAULT_MESSAGE_COLOR,ARight);
}
}
void C4IDList::Default()
{
Clear();
}
// Clear index entry and shift all entries behind down by one.
bool C4IDList::DeleteItem(size_t iIndex)
{
// invalid index
if (!Inside<size_t>(iIndex+1,1u,Count)) return false;
// get chunk to delete of
size_t index=iIndex;
C4IDListChunk *pQueryChunk=this;
while (index>=C4IDListChunkSize) { pQueryChunk=pQueryChunk->pNext; index-=C4IDListChunkSize; }
// shift down all entries behind
size_t cnt=--Count-iIndex,cntl=index,cntl2=cntl;
C4IDListChunk *pNextChunk=pQueryChunk;
while (cnt--)
{
// check for list overlap
if (++cntl2==C4IDListChunkSize)
{
pNextChunk=pQueryChunk->pNext;
cntl2=0;
}
// move down
pQueryChunk->id[cntl]=pNextChunk->id[cntl2];
pQueryChunk->Count[cntl]=pNextChunk->Count[cntl2];
// next item
pQueryChunk=pNextChunk; cntl=cntl2;
}
// done
return true;
}
bool C4IDList::operator==(const C4IDList& rhs) const
{
// compare counts
if (Count != rhs.Count) return false;
// compare all chunks
const C4IDListChunk *pChunk1 = this;
const C4IDListChunk *pChunk2 = &rhs;
int32_t cnt=Count;
while (pChunk1 && pChunk2)
{
if (memcmp(pChunk1->id, pChunk2->id, sizeof(C4ID)*std::min<int32_t>(cnt, C4IDListChunkSize)) ) return false;
if (memcmp(pChunk1->Count, pChunk2->Count, sizeof(int32_t)*std::min<int32_t>(cnt, C4IDListChunkSize)) ) return false;
pChunk1=pChunk1->pNext; pChunk2=pChunk2->pNext;
cnt-=C4IDListChunkSize;
}
// equal!
return true;
}
void C4IDList::CompileFunc(StdCompiler *pComp, bool fValues)
{
// Get compiler characteristics
bool deserializing = pComp->isDeserializer();
bool fNaming = pComp->hasNaming();
// Compiling: Clear existing data first
if (deserializing) Clear();
// Start
C4IDListChunk *pChunk = this;
size_t iNr = 0, iCNr = 0;
// Without naming: Compile Count
int32_t iCount = Count;
if (!fNaming) pComp->Value(iCount);
Count = iCount;
// Read
for (;;)
{
// Prepare compiling of single mapping
if (!deserializing)
{
// End of list?
if (iNr >= Count) break;
// End of chunk?
if (iCNr >= C4IDListChunkSize)
{
pChunk = pChunk->pNext;
iCNr = 0;
}
}
else
{
// End of list?
if (!fNaming) if (iNr >= Count) break;
// End of chunk?
if (iCNr >= C4IDListChunkSize)
{
pChunk = pChunk->pNext = new C4IDListChunk();
iCNr = 0;
}
}
// Separator (';')
if (iNr > 0) if (!pComp->Separator(StdCompiler::SEP_SEP2)) break;
// ID
pComp->Value(mkDefaultAdapt(pChunk->id[iCNr], C4ID::None));
// ID not valid? Note that C4ID::None is invalid.
if (pChunk->id[iCNr] == C4ID::None) break;
// Value: Skip this part if requested
if (fValues)
{
// Separator ('=')
if (pComp->Separator(StdCompiler::SEP_SET))
// Count
pComp->Value(mkDefaultAdapt(pChunk->Count[iCNr], 0));
}
else if (deserializing)
pChunk->Count[iCNr] = 0;
// Goto next entry
iNr++; iCNr++;
// Save back count
if (deserializing && fNaming) Count = iNr;
}
}