/* ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (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(); }