551 lines
15 KiB
C++

/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** 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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: Dict.cpp
//-----------------------------------------------------------------------------
//
// Westwood Studios Pacific.
//
// Confidential Information
// Copyright (C) 2001 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: RTS3
//
// File name: Dict.cpp
//
// Created: Steven Johnson, November 2001
//
// Desc: General-purpose dictionary class
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
#include "Common/Dict.h"
#include "Common/GameMemory.h"
// -----------------------------------------------------
void Dict::DictPair::copyFrom(DictPair* that)
{
Dict::DataType curType = this->getType();
Dict::DataType newType = that->getType();
if (curType != newType)
{
clear();
}
switch(newType)
{
case DICT_BOOL:
case DICT_INT:
case DICT_REAL:
*this = *that;
break;
case DICT_ASCIISTRING:
this->m_key = that->m_key;
*this->asAsciiString() = *that->asAsciiString();
break;
case DICT_UNICODESTRING:
this->m_key = that->m_key;
*this->asUnicodeString() = *that->asUnicodeString();
break;
}
}
// -----------------------------------------------------
void Dict::DictPair::clear()
{
switch (getType())
{
case DICT_BOOL:
case DICT_INT:
case DICT_REAL:
m_value = 0;
break;
case DICT_ASCIISTRING:
asAsciiString()->clear();
break;
case DICT_UNICODESTRING:
asUnicodeString()->clear();
break;
}
}
// -----------------------------------------------------
void Dict::DictPair::setNameAndType(NameKeyType key, Dict::DataType type)
{
Dict::DataType curType = getType();
if (curType != type)
{
clear();
}
m_key = createKey(key, type);
}
// -----------------------------------------------------
#ifdef _DEBUG
void Dict::validate() const
{
if (!m_data) return;
DEBUG_ASSERTCRASH(m_data->m_refCount > 0, ("m_refCount is zero"));
DEBUG_ASSERTCRASH(m_data->m_refCount < 32000, ("m_refCount is suspiciously large"));
DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated > 0, ("m_numPairsAllocated is zero"));
DEBUG_ASSERTCRASH(m_data->m_numPairsUsed >= 0, ("m_numPairsUsed is neg"));
DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated >= m_data->m_numPairsUsed, ("m_numPairsAllocated too small"));
DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated < 1024, ("m_numPairsAllocated suspiciously large"));
}
#endif
// -----------------------------------------------------
Dict::DictPair* Dict::findPairByKey(NameKeyType key) const
{
DEBUG_ASSERTCRASH(key != NAMEKEY_INVALID, ("invalid namekey!"));
DEBUG_ASSERTCRASH((UnsignedInt)key < (1L<<23), ("namekey too large!"));
if (!m_data)
return NULL;
DictPair* base = m_data->peek();
Int minIdx = 0;
Int maxIdx = m_data->m_numPairsUsed;
while (minIdx < maxIdx)
{
Int midIdx = (((minIdx + maxIdx) - 1) >> 1);
DictPair* mid = base + midIdx;
NameKeyType midKey = mid->getName();
if (key > midKey)
minIdx = midIdx + 1;
else if (key < midKey)
maxIdx = midIdx;
else
return mid;
}
return NULL;
}
// -----------------------------------------------------
Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPair *pairToTranslate)
{
if (m_data &&
m_data->m_refCount == 1 &&
m_data->m_numPairsAllocated >= numPairsNeeded)
{
// no buffer manhandling is needed (it's already large enough, and unique to us)
return pairToTranslate;
}
if (numPairsNeeded > MAX_LEN)
throw ERROR_OUT_OF_MEMORY;
Dict::DictPairData* newData = NULL;
if (numPairsNeeded > 0)
{
int minBytes = sizeof(Dict::DictPairData) + numPairsNeeded*sizeof(Dict::DictPair);
int actualBytes = TheDynamicMemoryAllocator->getActualAllocationSize(minBytes);
// note: be certain to alloc with zero; we'll take advantage of the fact that all-zero
// is a bit-pattern that happens to init all our pairs to legal values:
// type BOOL, key INVALID, value FALSE.
newData = (Dict::DictPairData*)TheDynamicMemoryAllocator->allocateBytes(actualBytes, "Dict::ensureUnique");
newData->m_refCount = 1;
newData->m_numPairsAllocated = (actualBytes - sizeof(Dict::DictPairData))/sizeof(Dict::DictPair);
newData->m_numPairsUsed = 0;
if (preserveData && m_data)
{
Dict::DictPair* src = m_data->peek();
Dict::DictPair* dst = newData->peek();
for (Int i = 0; i < m_data->m_numPairsUsed; ++i, ++src, ++dst)
dst->copyFrom(src);
newData->m_numPairsUsed = m_data->m_numPairsUsed;
}
}
Int delta;
if (pairToTranslate && m_data)
delta = pairToTranslate - m_data->peek();
releaseData();
m_data = newData;
if (pairToTranslate && m_data)
pairToTranslate = m_data->peek() + delta;
return pairToTranslate;
}
// -----------------------------------------------------
void Dict::clear()
{
releaseData();
m_data = NULL;
}
// -----------------------------------------------------
void Dict::releaseData()
{
if (m_data)
{
if (--m_data->m_refCount == 0)
{
Dict::DictPair* src = m_data->peek();
for (Int i = 0; i < m_data->m_numPairsUsed; ++i, ++src)
src->clear();
TheDynamicMemoryAllocator->freeBytes(m_data);
}
m_data = 0;
}
}
// -----------------------------------------------------
Dict::Dict(Int numPairsToPreAllocate) : m_data(0)
{
/*
This class plays some skanky games, in the name of memory and code
efficiency; it assumes all the data types will fit into a pointer.
This is currently true, but if that assumption ever changes, all hell
will break loose. So we do a quick check to assure this...
*/
DEBUG_ASSERTCRASH(sizeof(Bool) <= sizeof(void*) &&
sizeof(Int) <= sizeof(void*) &&
sizeof(Real) <= sizeof(void*) &&
sizeof(AsciiString) <= sizeof(void*) &&
sizeof(UnicodeString) <= sizeof(void*), ("oops, this code needs attention"));
if (numPairsToPreAllocate)
ensureUnique(numPairsToPreAllocate, false, NULL); // will throw on error
}
// -----------------------------------------------------
Dict& Dict::operator=(const Dict& src)
{
validate();
if (&src != this)
{
releaseData();
m_data = src.m_data;
if (m_data)
++m_data->m_refCount;
}
validate();
return *this;
}
// -----------------------------------------------------
Dict::DataType Dict::getType(NameKeyType key) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair)
return pair->getType();
return DICT_NONE;
}
// -----------------------------------------------------
Bool Dict::getBool(NameKeyType key, Bool *exists/*=NULL*/) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair && pair->getType() == DICT_BOOL)
{
if (exists) *exists = true;
return *pair->asBool();
}
DEBUG_ASSERTCRASH(exists != NULL, ("dict key missing, or of wrong type\n")); // only assert if they didn't check result
if (exists) *exists = false;
return false;
}
// -----------------------------------------------------
Int Dict::getInt(NameKeyType key, Bool *exists/*=NULL*/) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair && pair->getType() == DICT_INT)
{
if (exists) *exists = true;
return *pair->asInt();
}
DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n")); // only assert if they didn't check result
if (exists) *exists = false;
return 0;
}
// -----------------------------------------------------
Real Dict::getReal(NameKeyType key, Bool *exists/*=NULL*/) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair && pair->getType() == DICT_REAL)
{
if (exists) *exists = true;
return *pair->asReal();
}
DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n")); // only assert if they didn't check result
if (exists) *exists = false;
return 0.0f;
}
// -----------------------------------------------------
AsciiString Dict::getAsciiString(NameKeyType key, Bool *exists/*=NULL*/) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair && pair->getType() == DICT_ASCIISTRING)
{
if (exists) *exists = true;
return *pair->asAsciiString();
}
DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n")); // only assert if they didn't check result
if (exists) *exists = false;
return AsciiString::TheEmptyString;
}
// -----------------------------------------------------
UnicodeString Dict::getUnicodeString(NameKeyType key, Bool *exists/*=NULL*/) const
{
validate();
DictPair* pair = findPairByKey(key);
if (pair && pair->getType() == DICT_UNICODESTRING)
{
if (exists) *exists = true;
return *pair->asUnicodeString();
}
DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n")); // only assert if they didn't check result
if (exists) *exists = false;
return UnicodeString::TheEmptyString;
}
// -----------------------------------------------------
Bool Dict::getNthBool(Int n) const
{
validate();
DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
if (m_data)
{
DictPair* pair = &m_data->peek()[n];
if (pair && pair->getType() == DICT_BOOL)
return *pair->asBool();
}
DEBUG_CRASH(("dict key missing, or of wrong type\n"));
return false;
}
// -----------------------------------------------------
Int Dict::getNthInt(Int n) const
{
validate();
DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
if (m_data)
{
DictPair* pair = &m_data->peek()[n];
if (pair && pair->getType() == DICT_INT)
return *pair->asInt();
}
DEBUG_CRASH(("dict key missing, or of wrong type\n"));
return 0;
}
// -----------------------------------------------------
Real Dict::getNthReal(Int n) const
{
validate();
DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
if (m_data)
{
DictPair* pair = &m_data->peek()[n];
if (pair && pair->getType() == DICT_REAL)
return *pair->asReal();
}
DEBUG_CRASH(("dict key missing, or of wrong type\n"));
return 0.0f;
}
// -----------------------------------------------------
AsciiString Dict::getNthAsciiString(Int n) const
{
validate();
DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
if (m_data)
{
DictPair* pair = &m_data->peek()[n];
if (pair && pair->getType() == DICT_ASCIISTRING)
return *pair->asAsciiString();
}
DEBUG_CRASH(("dict key missing, or of wrong type\n"));
return AsciiString::TheEmptyString;
}
// -----------------------------------------------------
UnicodeString Dict::getNthUnicodeString(Int n) const
{
validate();
DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
if (m_data)
{
DictPair* pair = &m_data->peek()[n];
if (pair && pair->getType() == DICT_UNICODESTRING)
return *pair->asUnicodeString();
}
DEBUG_CRASH(("dict key missing, or of wrong type\n"));
return UnicodeString::TheEmptyString;
}
// -----------------------------------------------------
Dict::DictPair *Dict::setPrep(NameKeyType key, Dict::DataType type)
{
DictPair* pair = findPairByKey(key);
Int pairsNeeded = getPairCount();
if (!pair)
++pairsNeeded;
pair = ensureUnique(pairsNeeded, true, pair);
if (!pair)
{
pair = &m_data->peek()[m_data->m_numPairsUsed++];
}
pair->setNameAndType(key, type);
DEBUG_ASSERTCRASH(pair, ("pair must not be null here"));
return pair;
}
// -----------------------------------------------------
void Dict::sortPairs()
{
if (!m_data)
return;
// yer basic shellsort.
for (Int gap = m_data->m_numPairsUsed >> 1; gap > 0; gap >>= 1)
{
for (Int i = gap; i < m_data->m_numPairsUsed; i++)
{
for (Int j = i - gap; j >= 0; j -= gap)
{
DictPair* a = m_data->peek() + j;
DictPair* b = m_data->peek() + j + gap;
if (a->getName() > b->getName())
{
DictPair tmp = *a;
*a = *b;
*b = tmp;
}
else
{
break;
}
}
}
}
}
// -----------------------------------------------------
void Dict::setBool(NameKeyType key, Bool value)
{
validate();
DictPair* pair = setPrep(key, DICT_BOOL);
*pair->asBool() = value;
sortPairs();
validate();
}
// -----------------------------------------------------
void Dict::setInt(NameKeyType key, Int value)
{
validate();
DictPair* pair = setPrep(key, DICT_INT);
*pair->asInt() = value;
sortPairs();
validate();
}
// -----------------------------------------------------
void Dict::setReal(NameKeyType key, Real value)
{
validate();
DictPair* pair = setPrep(key, DICT_REAL);
*pair->asReal() = value;
sortPairs();
validate();
}
// -----------------------------------------------------
void Dict::setAsciiString(NameKeyType key, const AsciiString& value)
{
validate();
DictPair* pair = setPrep(key, DICT_ASCIISTRING);
*pair->asAsciiString() = value;
sortPairs();
validate();
}
// -----------------------------------------------------
void Dict::setUnicodeString(NameKeyType key, const UnicodeString& value)
{
validate();
DictPair* pair = setPrep(key, DICT_UNICODESTRING);
*pair->asUnicodeString() = value;
sortPairs();
validate();
}
// -----------------------------------------------------
Bool Dict::remove(NameKeyType key)
{
validate();
DictPair* pair = findPairByKey(key);
if (pair)
{
pair = ensureUnique(m_data->m_numPairsUsed, true, pair);
pair->setNameAndType((NameKeyType)0x7fffffff, DICT_BOOL);
sortPairs();
--m_data->m_numPairsUsed;
validate();
return true;
}
DEBUG_CRASH(("dict key missing in remove\n"));
return false;
}
// -----------------------------------------------------
void Dict::copyPairFrom(const Dict& that, NameKeyType key)
{
this->validate();
DictPair* thatPair = that.findPairByKey(key);
if (thatPair)
{
DictPair* thisPair = this->setPrep(key, thatPair->getType());
thisPair->copyFrom(thatPair);
this->sortPairs();
}
else
{
if (this->findPairByKey(key))
this->remove(key);
}
this->validate();
}