PageRenderTime 147ms CodeModel.GetById 36ms app.highlight 99ms RepoModel.GetById 1ms app.codeStats 1ms

/Rainman2/luattrib.cpp

http://modstudio2.googlecode.com/
C++ | 1310 lines | 1129 code | 125 blank | 56 comment | 212 complexity | 0f16f12841bf24b254b013450a0bd0f6 MD5 | raw file
   1/*
   2Copyright (c) 2008 Peter "Corsix" Cawley
   3
   4Permission is hereby granted, free of charge, to any person
   5obtaining a copy of this software and associated documentation
   6files (the "Software"), to deal in the Software without
   7restriction, including without limitation the rights to use,
   8copy, modify, merge, publish, distribute, sublicense, and/or sell
   9copies of the Software, and to permit persons to whom the
  10Software is furnished to do so, subject to the following
  11conditions:
  12
  13The above copyright notice and this permission notice shall be
  14included in all copies or substantial portions of the Software.
  15
  16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23OTHER DEALINGS IN THE SOFTWARE.
  24*/
  25#include "luattrib.h"
  26#ifdef RAINMAN2_USE_LUA
  27extern "C" {
  28#include <lua.h>
  29#include <lauxlib.h>
  30#include <lobject.h>
  31#include <lopcodes.h>
  32#include <lstate.h>
  33}
  34#include <math.h>
  35#include <new>
  36#include <algorithm>
  37#include <locale>
  38#include <limits>
  39#include "hash.h"
  40#include "exception.h"
  41#include "rgd_dict.h"
  42#include "binaryattrib.h"
  43#pragma warning(disable: 4996)
  44
  45class LuaAttribTableAdapter : public IAttributeTable
  46{
  47public:
  48  LuaAttribTableAdapter(LuaAttrib::_table_t *pTable, LuaAttrib *pFile);
  49  ~LuaAttribTableAdapter();
  50
  51  unsigned long getChildCount() throw();
  52  IAttributeValue* getChild(unsigned long iIndex) throw(...);
  53  void addChild(unsigned long iName) throw(...);
  54  void deleteChild(unsigned long iIndex, bool bRevertIsSufficient) throw(...);
  55  unsigned long findChildIndex(unsigned long iName) throw();
  56
  57protected:
  58  void _sortValues();
  59
  60  std::vector<unsigned long> m_vValues;
  61  LuaAttrib::_table_t *m_pTable;
  62  LuaAttrib *m_pFile;
  63};
  64
  65class LuaAttribValueAdapter : public IAttributeValue
  66{
  67public:
  68  LuaAttribValueAdapter(LuaAttrib::_value_t *pValue, LuaAttrib::_table_t *pParent, unsigned long iName, LuaAttrib *pFile)
  69    : m_pValue(pValue), m_pParent(pParent), m_iName(iName), m_pFile(pFile)
  70  {
  71    m_bConst = !pParent || (pParent->mapContents.count(iName) == 0);
  72  }
  73  ~LuaAttribValueAdapter()
  74  {
  75  }
  76
  77  unsigned long        getName()      const throw()
  78  {
  79    return m_iName;
  80  }
  81
  82  eAttributeValueTypes getType()      const throw()
  83  {
  84    switch(m_pValue->eType)
  85    {
  86    case LuaAttrib::_value_t::T_String:
  87      return VT_String;
  88    case LuaAttrib::_value_t::T_Boolean:
  89      return VT_Boolean;
  90    case LuaAttrib::_value_t::T_Float:
  91      return VT_Float;
  92    case LuaAttrib::_value_t::T_Table:
  93      return VT_Table;
  94    default:
  95      return VT_Unknown;
  96    }
  97  }
  98
  99  RainString           getFileSetIn() const throw()
 100  {
 101    if(m_pFile)
 102      return m_pFile->getName();
 103    return L"";
 104  }
 105
 106  eAttributeValueIcons getIcon()      const throw()
 107  {
 108    if(isTable())
 109    {
 110      if(m_pValue->pValue->bTotallyUnchaged)
 111        return VI_SameAsParent;
 112      else
 113        return VI_Table;
 114    }
 115    if(m_pParent->mapContents.count(m_iName) == 0)
 116      return VI_SameAsParent;
 117    const LuaAttrib::_table_t *pGrandparent = _findGrandparentWithKey();
 118    if(!pGrandparent)
 119      return VI_NewSinceParent;
 120    const LuaAttrib::_value_t &oValue = ((LuaAttrib::_table_t *)pGrandparent)->mapContents[m_iName];
 121    if(m_pValue->eType != oValue.eType)
 122      return VI_DifferentToParent;
 123    switch(getType())
 124    {
 125    case VT_String:
 126      return (m_pValue->sValue == oValue.sValue || (m_pValue->iLength == oValue.iLength && strcmp(oValue.sValue, m_pValue->sValue) == 0)) ? VI_SameAsParent : VI_DifferentToParent;
 127    case VT_Float:
 128      return (m_pValue->fValue == oValue.fValue) ? VI_SameAsParent : VI_DifferentToParent;
 129    case VT_Boolean:
 130      return (m_pValue->bValue == oValue.bValue) ? VI_SameAsParent : VI_DifferentToParent;
 131    default:
 132      return VI_DifferentToParent;
 133    };
 134  }
 135
 136  const IAttributeValue* getParent() const throw(...)
 137  {
 138    LuaAttrib::_table_t *pGrandparent = (LuaAttrib::_table_t*)_findGrandparentWithKey();
 139    if(!pGrandparent)
 140      THROW_SIMPLE(L"Value is not present in any parent");
 141    return CHECK_ALLOCATION(new (std::nothrow) LuaAttribValueAdapter(&pGrandparent->mapContents[m_iName], pGrandparent, m_iName, pGrandparent->pSourceFile));
 142  }
 143
 144  const IAttributeValue* getParentNoThrow() const throw()
 145  {
 146    LuaAttrib::_table_t *pGrandparent = (LuaAttrib::_table_t*)_findGrandparentWithKey();
 147    if(!pGrandparent)
 148      return 0;
 149    return (new (std::nothrow) LuaAttribValueAdapter(&pGrandparent->mapContents[m_iName], pGrandparent, m_iName, pGrandparent->pSourceFile));
 150  }
 151
 152  RainString       getValueString () const throw(...)
 153  {
 154    CHECK_ASSERT(m_pValue->eType == LuaAttrib::_value_t::T_String);
 155    return RainString(m_pValue->sValue, m_pValue->iLength);
 156  }
 157
 158  bool             getValueBoolean() const throw(...)
 159  {
 160    CHECK_ASSERT(m_pValue->eType == LuaAttrib::_value_t::T_Boolean);
 161    return m_pValue->bValue;
 162  }
 163
 164  IAttributeTable* getValueTable  () const throw(...)
 165  {
 166    CHECK_ASSERT(m_pValue->eType == LuaAttrib::_value_t::T_Table);
 167    return new LuaAttribTableAdapter(m_pValue->pValue, m_pFile);
 168  }
 169
 170  float            getValueFloat  () const throw(...)
 171  {
 172    CHECK_ASSERT(m_pValue->eType == LuaAttrib::_value_t::T_Float);
 173    return m_pValue->fValue;
 174  }
 175
 176  long            getValueInteger  () const throw(...)
 177  {
 178    THROW_SIMPLE(L"Value is not integer");
 179  }
 180
 181  virtual void setName(unsigned long iName          ) throw(...)
 182  {
 183    if(!m_pParent)
 184      THROW_SIMPLE(L"Cannot change the name of this item");
 185    m_pParent->mapContents[iName] = *m_pValue;
 186    m_pParent->mapContents.erase(m_iName);
 187    m_iName = iName;
 188  }
 189
 190  virtual void setType(eAttributeValueTypes eNewType) throw(...)
 191  {
 192    if(getType() != eNewType)
 193    {
 194      _checkConst();
 195      m_pValue->free();
 196      switch(eNewType)
 197      {
 198      case VT_String:
 199        m_pValue->sValue = "";
 200        break;
 201      case VT_Boolean:
 202        m_pValue->bValue = false;
 203        break;
 204      case VT_Table:
 205        CHECK_ALLOCATION(m_pValue->pValue = new (std::nothrow) LuaAttrib::_table_t(m_pFile));
 206        break;
 207      case VT_Float:
 208        m_pValue->fValue = 0.0f;
 209        break;
 210      };
 211    }
 212  }
 213
 214  virtual void setValueString (const RainString& sValue) throw(...)
 215  {
 216    setType(VT_String);
 217    _checkConst();
 218    lua_State *L = m_pFile->getCache()->getLuaState();
 219    lua_checkstack(L, 8);
 220    lua_pushlightuserdata(L, reinterpret_cast<void*>(m_pFile));
 221    lua_gettable(L, LUA_REGISTRYINDEX);
 222    if(lua_type(L, -1) == LUA_TNIL)
 223    {
 224      lua_pop(L, 1);
 225      lua_newtable(L);
 226      lua_pushlightuserdata(L, reinterpret_cast<void*>(m_pFile));
 227      lua_pushvalue(L, -2);
 228      lua_settable(L, LUA_REGISTRYINDEX);
 229    }
 230    luaL_Buffer b;
 231    luaL_buffinit(L, &b);
 232    for(size_t i = 0; i < sValue.length(); ++i)
 233      luaL_addchar(&b, sValue.getCharacters()[i]);
 234    luaL_pushresult(&b);
 235    m_pValue->sValue = lua_tostring(L, -1);
 236    m_pValue->iLength = static_cast<long>(sValue.length());
 237    lua_rawseti(L, -2, static_cast<int>(lua_objlen(L, -2) + 1));
 238    lua_pop(L, 1);
 239  }
 240
 241  virtual void setValueBoolean(bool bValue             ) throw(...)
 242  {
 243    setType(VT_Boolean);
 244    _checkConst();
 245    m_pValue->bValue = bValue;
 246  }
 247
 248  virtual void setValueFloat  (float fValue            ) throw(...)
 249  {
 250    setType(VT_Float);
 251    _checkConst();
 252    m_pValue->fValue = fValue;
 253  }
 254
 255  void _checkConst()
 256  {
 257    if(m_bConst)
 258    {
 259      if(!m_pParent)
 260        THROW_SIMPLE(L"Value is immutable");
 261      m_pParent->mapContents[m_iName] = *m_pValue;
 262      m_pValue = &m_pParent->mapContents[m_iName];
 263      m_pFile = m_pParent->pSourceFile;
 264      m_bConst = false;
 265    }
 266  }
 267
 268  const LuaAttrib::_table_t* _findGrandparentWithKey() const
 269  {
 270    const LuaAttrib::_table_t *pTable = m_pParent;
 271    for(; pTable; pTable = pTable->pInheritFrom)
 272    {
 273      if(pTable->mapContents.count(m_iName))
 274      {
 275        for(pTable = pTable->pInheritFrom; pTable; pTable = pTable->pInheritFrom)
 276        {
 277          if(pTable->mapContents.count(m_iName))
 278            return pTable;
 279        }
 280        return 0;
 281      }
 282    }
 283    return 0;
 284  }
 285
 286protected:
 287  LuaAttrib::_value_t *m_pValue;
 288  LuaAttrib::_table_t *m_pParent;
 289  unsigned long m_iName;
 290  bool m_bConst;
 291  LuaAttrib *m_pFile;
 292};
 293
 294LuaAttribTableAdapter::LuaAttribTableAdapter(LuaAttrib::_table_t *_pTable, LuaAttrib *pFile)
 295  : m_pTable(_pTable), m_pFile(pFile)
 296{
 297  std::map<unsigned long, bool> mapKeys;
 298  for(const LuaAttrib::_table_t* pTable = _pTable; pTable; pTable = pTable->pInheritFrom)
 299  {
 300    for(std::map<unsigned long, LuaAttrib::_value_t>::const_iterator itr = pTable->mapContents.begin(); itr != pTable->mapContents.end(); ++itr)
 301    {
 302      if(mapKeys.count(itr->first) == 0)
 303      {
 304        mapKeys[itr->first] = true;
 305        m_vValues.push_back(itr->first);
 306      }
 307    }
 308  }
 309  _sortValues();
 310}
 311
 312LuaAttribTableAdapter::~LuaAttribTableAdapter()
 313{
 314}
 315
 316unsigned long LuaAttribTableAdapter::getChildCount() throw()
 317{
 318  return static_cast<unsigned long>(m_vValues.size());
 319}
 320
 321IAttributeValue* LuaAttribTableAdapter::getChild(unsigned long iIndex) throw(...)
 322{
 323  CHECK_RANGE_LTMAX(0, iIndex, getChildCount());
 324  unsigned long iKey = m_vValues[iIndex];
 325  for(LuaAttrib::_table_t* pTable = m_pTable; pTable; pTable = const_cast<LuaAttrib::_table_t*>(pTable->pInheritFrom))
 326  {
 327    if(pTable->mapContents.count(iKey))
 328    {
 329      LuaAttrib::_value_t *pValue = &pTable->mapContents[iKey];
 330      if(pValue->eType == LuaAttrib::_value_t::T_Table && pTable != m_pTable)
 331      {
 332        LuaAttrib::_value_t oValue = *pValue;
 333        m_pFile->_wraptable(&oValue);
 334        oValue.pValue->bTotallyUnchaged = true;
 335        m_pTable->mapContents[iKey] = oValue;
 336        pTable = m_pTable;
 337        pValue = &pTable->mapContents[iKey];
 338      }
 339      return CHECK_ALLOCATION(new (std::nothrow) LuaAttribValueAdapter(pValue, m_pTable, iKey, pTable->pSourceFile));
 340    }
 341  }
 342  THROW_SIMPLE(L"Child not found - should never happen");
 343}
 344
 345void LuaAttribTableAdapter::addChild(unsigned long iName) throw(...)
 346{
 347  if(m_pTable->mapContents.count(iName))
 348    THROW_SIMPLE(L"Cannot add a child which already exists");
 349  m_pTable->mapContents[iName] = LuaAttrib::_value_t();
 350  m_vValues.push_back(iName);
 351  _sortValues();
 352}
 353
 354void LuaAttribTableAdapter::deleteChild(unsigned long iIndex, bool bRevertIsSufficient) throw(...)
 355{
 356  CHECK_RANGE_LTMAX(0, iIndex, getChildCount());
 357  unsigned long iKey = m_vValues[iIndex];
 358  bool bInTopLevel, bInFurtherLevels = false;
 359  bInTopLevel = m_pTable->mapContents.count(iKey) == 1;
 360  for(const LuaAttrib::_table_t* pTable = m_pTable->pInheritFrom; pTable; pTable = pTable->pInheritFrom)
 361  {
 362    bInFurtherLevels = bInFurtherLevels || (pTable->mapContents.count(iKey) == 1);
 363  }
 364  if(bInFurtherLevels)
 365  {
 366    if(!bInTopLevel || !bRevertIsSufficient)
 367      THROW_SIMPLE(L"Cannot delete child as it is set in inherited files");
 368    m_pTable->mapContents.erase(iKey);
 369  }
 370  else
 371  {
 372    m_pTable->mapContents.erase(iKey);
 373    m_vValues.erase(m_vValues.begin() + iIndex);
 374  }
 375}
 376
 377unsigned long LuaAttribTableAdapter::findChildIndex(unsigned long iName) throw()
 378{
 379  unsigned long iIndex = 0;
 380  for(std::vector<unsigned long>::iterator itr = m_vValues.begin(); itr != m_vValues.end(); ++itr, ++iIndex)
 381  {
 382    if(*itr == iName)
 383      return iIndex;
 384  }
 385  return NO_INDEX;
 386}
 387
 388//! Alphabetical comparison function for RGD hashes
 389/*!
 390  Sorting with this function as the predicate causes hashes with
 391  no known text equivalent to be placed first, in ascending order,
 392  followed by hashes with known text equivalents, in alphabetical
 393  order.
 394*/
 395static bool HashSortLessThen(unsigned long i1, unsigned long i2)
 396{
 397  RgdDictionary *pDictionary = RgdDictionary::getSingleton();
 398  const char* s1 = pDictionary->hashToAsciiNoThrow(i1);
 399  const char* s2 = pDictionary->hashToAsciiNoThrow(i2);
 400
 401  // Two strings => lexicographical less-than
 402  if(s1 && s2)
 403    return stricmp(s1, s2) < 0;
 404  else
 405  {
 406    // One hash and one string => hash comes first
 407    if(s1)
 408      return false;
 409    else if(s2)
 410      return true;
 411
 412    // Two hashes => smaller one first
 413    return i1 < i2;
 414  }
 415}
 416
 417//! HashSortLessThen as a function object
 418struct HashSortLessThen_t
 419{
 420  bool operator()(unsigned long a, unsigned long b) const
 421  {
 422    return HashSortLessThen(a, b);
 423  }
 424};
 425
 426void LuaAttribTableAdapter::_sortValues()
 427{
 428  std::sort(m_vValues.begin(), m_vValues.end(), HashSortLessThen);
 429}
 430
 431LuaAttribCache::LuaAttribCache()
 432{
 433  m_L = 0;
 434  m_pDirectory = 0;
 435  m_oEmptyTable.eType = LuaAttrib::_value_t::T_Table;
 436  m_oEmptyTable.pValue = new LuaAttrib::_table_t(0);
 437  m_pEmptyFile = new LuaAttrib;
 438  m_pEmptyFile->setName(L"");
 439  m_oEmptyTable.pValue->pSourceFile = m_pEmptyFile;
 440}
 441
 442LuaAttribCache::~LuaAttribCache()
 443{
 444  delete m_pEmptyFile;
 445  for(std::map<unsigned long, LuaAttrib*>::iterator itr = m_mapFiles.begin(); itr != m_mapFiles.end(); ++itr)
 446    delete itr->second;
 447  if(m_L)
 448    lua_close(m_L);
 449}
 450
 451lua_State* LuaAttribCache::getLuaState()
 452{
 453  if(!m_L)
 454    m_L = luaL_newstate();
 455  return m_L;
 456}
 457
 458void LuaAttribCache::setBaseFolder(IDirectory *pFolder)
 459{
 460  m_pDirectory = pFolder;
 461}
 462
 463LuaAttrib* LuaAttribCache::_performGet(const char* sFilename)
 464{
 465  if(sFilename[0] == 0)
 466    return 0;
 467  unsigned long iHash = CRCCaselessHashSimple((const void*)sFilename, strlen(sFilename));
 468  LuaAttrib* pAttribFile = m_mapFiles[iHash];
 469  if(!pAttribFile)
 470  {
 471    IFile* pFile = 0;
 472    try
 473    {
 474      pFile = m_pDirectory->getStore()->openFile(m_pDirectory->getPath() + RainString(sFilename), FM_Read);
 475    }
 476    CATCH_THROW_SIMPLE_({}, L"Cannot open attrib file \'%S\'", sFilename);
 477    lua_State *L = getLuaState();
 478    int iLuaStackTop = lua_gettop(L);
 479    try
 480    {
 481      CHECK_ALLOCATION(pAttribFile = new LuaAttrib);
 482      pAttribFile->setName(sFilename);
 483      pAttribFile->setBaseFolder(m_pDirectory);
 484      pAttribFile->setCache(this, false);
 485      pAttribFile->loadFromFile(pFile);
 486      delete pFile;
 487      pFile = 0;
 488    }
 489    CATCH_THROW_SIMPLE_({delete pAttribFile; lua_settop(L, iLuaStackTop); delete pFile;}, L"Cannot load attrib file \'%S\'", sFilename);
 490    int iNewStackTop = lua_gettop(L);
 491    if(iNewStackTop > iLuaStackTop)
 492    {
 493      lua_checkstack(L, 5);
 494      lua_createtable(L, iNewStackTop - iLuaStackTop, 0);
 495      lua_pushlightuserdata(L, (void*)pAttribFile);
 496      lua_pushvalue(L, -2);
 497      lua_settable(L, LUA_REGISTRYINDEX);
 498      for(int iIndex = (iLuaStackTop + 1); iIndex <= iNewStackTop; ++iIndex)
 499      {
 500        lua_pushvalue(L, iIndex);
 501        lua_rawseti(L, -2, iIndex - iLuaStackTop);
 502      }
 503      lua_settop(L, iLuaStackTop);
 504    }
 505    else if(iNewStackTop < iLuaStackTop)
 506    {
 507      delete pAttribFile;
 508      THROW_SIMPLE_(L"LuaAttrib loading \'%S\' popped items off the stack", sFilename);
 509    }
 510    m_mapFiles[iHash] = pAttribFile;
 511  }
 512  return pAttribFile;
 513}
 514
 515LuaAttrib::_value_t LuaAttribCache::getMetaData(const char* sFilename)
 516{
 517  LuaAttrib *pFile = _performGet(sFilename);
 518  return pFile ? pFile->m_oMetaData : m_oEmptyTable;
 519}
 520
 521LuaAttrib::_value_t LuaAttribCache::getGameData(const char* sFilename)
 522{
 523  LuaAttrib *pFile = _performGet(sFilename);
 524  return pFile ? pFile->m_oGameData : m_oEmptyTable;
 525}
 526
 527size_t LuaAttribCache::writeTableToBinaryFile(const LuaAttrib::_table_t* pTable, IFile* pFile, bool bCacheResult)
 528{
 529  while(pTable->bTotallyUnchaged && pTable->pInheritFrom)
 530  {
 531    pTable = pTable->pInheritFrom;
 532    bCacheResult = true;
 533  }
 534  try
 535  {
 536    if(m_mapTables.count(pTable) != 0)
 537    {
 538      MemoryWriteFile *pCached = m_mapTables[pTable];
 539      pFile->write(pCached->getBuffer(), 1, pCached->tell());
 540      return pCached->tell();
 541    }
 542    else
 543    {
 544      if(bCacheResult)
 545      {
 546        MemoryWriteFile *pCached = new MemoryWriteFile;
 547        m_mapTables[pTable] = pCached;
 548        pTable->writeToBinary(pCached);
 549        pFile->write(pCached->getBuffer(), 1, pCached->tell());
 550        return pCached->tell();
 551      }
 552      else
 553      {
 554        return pTable->writeToBinary(pFile);
 555      }
 556    }
 557  }
 558  catch(RainException *pE)
 559  {
 560    RETHROW_SIMPLE(pE, L"Error writing Lua table to binary file");
 561  }
 562}
 563
 564LuaAttrib::LuaAttrib() throw()
 565{
 566  m_pDirectory = 0;
 567  m_pCache = 0;
 568  m_bOwnCache = false;
 569}
 570
 571LuaAttrib::~LuaAttrib() throw()
 572{
 573  if(m_bOwnCache)
 574    delete m_pCache;
 575}
 576
 577IAttributeValue* LuaAttrib::getGameData() throw(...)
 578{
 579  return new LuaAttribValueAdapter(&m_oGameData, 0, RgdDictionary::getSingleton()->asciiToHash("GameData"), this);
 580}
 581
 582IAttributeValue* LuaAttrib::getMetaData() throw(...)
 583{
 584  return new LuaAttribValueAdapter(&m_oMetaData, 0, RgdDictionary::getSingleton()->asciiToHash("MetaData"), this);
 585}
 586
 587void LuaAttrib::setName(const RainString& sName) throw()
 588{
 589  m_sName = sName;
 590}
 591
 592RainString& LuaAttrib::getName() throw()
 593{
 594  return m_sName;
 595}
 596
 597LuaAttribCache* LuaAttrib::getCache() throw(...)
 598{
 599  if(!m_pCache)
 600  {
 601    CHECK_ALLOCATION(m_pCache = new (std::nothrow) LuaAttribCache);
 602    m_pCache->setBaseFolder(m_pDirectory);
 603    m_bOwnCache = true;
 604  }
 605  return m_pCache;
 606}
 607
 608void LuaAttrib::setCache(LuaAttribCache* pCache, bool bOwnCache) throw(...)
 609{
 610  CHECK_ASSERT(m_pCache == 0 && pCache != 0);
 611  m_pCache = pCache;
 612  m_bOwnCache = bOwnCache;
 613}
 614
 615void LuaAttrib::setBaseFolder(IDirectory *pFolder) throw()
 616{
 617  m_pDirectory = pFolder;
 618}
 619
 620template <class TIn, class TOut>
 621static TOut static_cast_tolower(TIn v)
 622{
 623  return std::tolower(static_cast<TOut>(v), std::locale::classic());
 624}
 625
 626//! Calculates the length of a string literal at compile-time, rather than leaving it to runtime
 627/*!
 628  \param s A string literal
 629  Returns two values; the literal itself, and the length of the literal, *not* including the
 630  NULL terminator (hence the -1).
 631*/
 632#define LITERAL(s) (s), ((sizeof(s)/sizeof(*(s)))-1)
 633
 634void LuaAttrib::saveToTextFile(IFile *pFile) throw(...)
 635{
 636  CHECK_ASSERT(m_oGameData.eType == _value_t::T_Table && m_oMetaData.eType == _value_t::T_Table);
 637
 638  try
 639  {
 640    BufferingOutputStream<char> oOutput(pFile);
 641    oOutput.write(LITERAL("----------------------------------------\r\n"));
 642    oOutput.write(LITERAL("-- File: \'"));
 643    oOutput.writeConverting(m_sName.getCharacters(), m_sName.length(), static_cast_tolower<RainChar, char>);
 644    oOutput.write(LITERAL("\'\r\n"));
 645    oOutput.write(LITERAL("-- Created by: Corsix\'s LuaEdit\r\n"));
 646    oOutput.write(LITERAL("-- Note: Feel free to edit by hand!\r\n"));
 647    oOutput.write(LITERAL("-- Partially or fully (c) Relic Entertainment Inc.\r\n\r\n"));
 648    _writeInheritLine(oOutput, m_oGameData, false);
 649    _writeInheritLine(oOutput, m_oMetaData, true);
 650    m_oGameData.pValue->writeToText(oOutput, LITERAL("GameData"));
 651    oOutput.write(LITERAL("\r\n"));
 652    _writeMetaData(oOutput);
 653  }
 654  CATCH_THROW_SIMPLE({}, L"Error encountered while writing Lua data");
 655}
 656
 657void LuaAttrib::_writeInheritLine(BufferingOutputStream<char>& oOutput, LuaAttrib::_value_t& oTable, bool bIsMetaData)
 658{
 659  if(bIsMetaData)
 660    oOutput.write(LITERAL("MetaData = InheritMeta([["));
 661  else
 662    oOutput.write(LITERAL("GameData = Inherit([["));
 663  if(oTable.pValue->pInheritFrom)
 664  {
 665    RainString &sInheritFrom = oTable.pValue->pInheritFrom->pSourceFile->m_sName;
 666    oOutput.writeConverting(sInheritFrom.getCharacters(), sInheritFrom.length(), static_cast_tolower<RainChar, char>);
 667  }
 668  oOutput.write("]])\r\n\r\n", bIsMetaData ? 7 : 5);
 669}
 670
 671void LuaAttrib::_writeMetaData(BufferingOutputStream<char>& oOutput)
 672{
 673  // Re-order the keys alphabetically, stripping out $REF
 674  std::map<unsigned long, _value_t*, HashSortLessThen_t> mapKeys;
 675  for(std::map<unsigned long, _value_t>::iterator itr = m_oMetaData.pValue->mapContents.begin(); itr != m_oMetaData.pValue->mapContents.end(); ++itr)
 676  {
 677    if(itr->first != RgdDictionary::_REF)
 678      mapKeys[itr->first] = &itr->second;
 679  }
 680
 681  // Print keys
 682  for(std::map<unsigned long, _value_t*, HashSortLessThen_t>::iterator itr = mapKeys.begin(); itr != mapKeys.end(); ++itr)
 683  {
 684    if(itr->first == RgdDictionary::_REF)
 685      continue;
 686
 687    oOutput.write(LITERAL("MetaData[\""));
 688    oOutput.write(RgdDictionary::getSingleton()->hashToAscii(itr->first));
 689    oOutput.write(LITERAL("\"] = "));
 690
 691    CHECK_ASSERT(itr->second->eType == _value_t::T_Table && "MetaData children should all be tables");
 692
 693    itr->second->pValue->writeToTextAsMetaDataTable(oOutput);
 694  }
 695}
 696
 697static bool StringHasSpecialChars(const char* s)
 698{
 699  for(; *s; ++s)
 700  {
 701    if(*s == '\\' || *s == '\r' || *s == '\n' || *s == '\"')
 702      return true;
 703  }
 704  return false;
 705}
 706
 707void LuaAttrib::_table_t::writeToTextAsMetaDataTable(BufferingOutputStream<char>& oOutput)
 708{
 709  oOutput.write(LITERAL("{"));
 710  for(std::map<unsigned long, _value_t>::iterator itr = mapContents.begin(); itr != mapContents.end(); ++itr)
 711  {
 712    oOutput.write(RgdDictionary::getSingleton()->hashToAscii(itr->first));
 713    oOutput.write(LITERAL(" = "));
 714    switch(itr->second.eType)
 715    {
 716    case LuaAttrib::_value_t::T_Float: {
 717      float fValue = itr->second.fValue;
 718      char sBuffer[64];
 719      if(fValue == floor(fValue) && fValue <= static_cast<float>(std::numeric_limits<long>::max()) && fValue >= static_cast<float>(std::numeric_limits<long>::min()))
 720      {
 721        sprintf(sBuffer, "%li", static_cast<long>(fValue));
 722      }
 723      else
 724      {
 725        sprintf(sBuffer, "%.3f", fValue);
 726      }
 727      oOutput.write(sBuffer);
 728      break; }
 729
 730    case LuaAttrib::_value_t::T_String:
 731      oOutput.write(LITERAL("[["));
 732      oOutput.write(itr->second.sValue, itr->second.iLength);
 733      oOutput.write(LITERAL("]]"));
 734      break;
 735
 736    case LuaAttrib::_value_t::T_Boolean:
 737      if(itr->second.bValue)
 738        oOutput.write(LITERAL("true"));
 739      else
 740        oOutput.write(LITERAL("false"));
 741      break;
 742
 743    case LuaAttrib::_value_t::T_Integer: {
 744      char sBuffer[64];
 745      sprintf(sBuffer, "%li", itr->second.iValue);
 746      break; }
 747
 748    case LuaAttrib::_value_t::T_Table:
 749      itr->second.pValue->writeToTextAsMetaDataTable(oOutput);
 750      break;
 751
 752    default:
 753      THROW_SIMPLE_(L"Cannot write meta data of type %i to text file", static_cast<int>(itr->second.eType));
 754    }
 755    oOutput.write(LITERAL(", "));
 756  }
 757  oOutput.write(LITERAL("}\r\n"));
 758}
 759
 760void LuaAttrib::_table_t::writeToText(BufferingOutputStream<char>& oOutput, const char* sPrefix, size_t iPrefixLength)
 761{
 762  // Re-order the keys alphabetically, stripping out $REF
 763  std::map<unsigned long, _value_t*, HashSortLessThen_t> mapKeys;
 764  for(std::map<unsigned long, _value_t>::iterator itr = mapContents.begin(); itr != mapContents.end(); ++itr)
 765  {
 766    if(itr->first != RgdDictionary::_REF)
 767      mapKeys[itr->first] = &itr->second;
 768  }
 769
 770  // Print keys
 771  for(std::map<unsigned long, _value_t*, HashSortLessThen_t>::iterator itr = mapKeys.begin(); itr != mapKeys.end(); ++itr)
 772  {
 773    if(itr->second->eType != _value_t::T_Table || itr->second->pValue->mapContents.count(RgdDictionary::_REF) == 1)
 774    {
 775      oOutput.write(sPrefix, iPrefixLength);
 776      oOutput.write(LITERAL("[\""));
 777      oOutput.write(RgdDictionary::getSingleton()->hashToAscii(itr->first));
 778      oOutput.write(LITERAL("\"] = "));
 779      switch(itr->second->eType)
 780      {
 781      case _value_t::T_Float: {
 782        char sBuffer[64];
 783        sprintf(sBuffer, "%.5f", itr->second->fValue);
 784        oOutput.write(sBuffer);
 785        break; }
 786
 787      case _value_t::T_String:
 788        if(StringHasSpecialChars(itr->second->sValue))
 789        {
 790          oOutput.write(LITERAL("[["));
 791          oOutput.write(itr->second->sValue, itr->second->iLength);
 792          oOutput.write(LITERAL("]]"));
 793        }
 794        else
 795        {
 796          oOutput.write(LITERAL("\""));
 797          oOutput.write(itr->second->sValue, itr->second->iLength);
 798          oOutput.write(LITERAL("\""));
 799        }
 800        break;
 801
 802      case _value_t::T_Boolean:
 803        if(itr->second->bValue)
 804          oOutput.write(LITERAL("true"));
 805        else
 806          oOutput.write(LITERAL("false"));
 807        break;
 808
 809      case _value_t::T_Integer: {
 810        char sBuffer[64];
 811        sprintf(sBuffer, "%li", itr->second->iValue);
 812        break; }
 813
 814      case _value_t::T_Table: {
 815        oOutput.write(LITERAL("Reference([["));
 816        RainString sReferenceFrom = itr->second->pValue->pInheritFrom->pSourceFile->m_sName;
 817        oOutput.writeConverting(sReferenceFrom.getCharacters(), sReferenceFrom.length(), static_cast_tolower<RainChar, char>);
 818        oOutput.write(LITERAL("]])"));
 819        break; }
 820
 821      default:
 822        THROW_SIMPLE_(L"Cannot write data of type %i to text file", static_cast<int>(itr->second->eType));
 823      }
 824      oOutput.write(LITERAL("\r\n"));
 825    }
 826    if(itr->second->eType == _value_t::T_Table)
 827    {
 828      _table_t *pChildTable = itr->second->pValue;
 829      if(!pChildTable->bTotallyUnchaged)
 830      {
 831        const char *sName = RgdDictionary::getSingleton()->hashToAscii(itr->first);
 832        size_t iNameLength = strlen(sName);
 833        char *sNewPrefix = CHECK_ALLOCATION(new (std::nothrow) char[iPrefixLength + 5 + iNameLength]);
 834        sprintf(sNewPrefix, "%s[\"%s\"]", sPrefix, sName);
 835        try
 836        {
 837          pChildTable->writeToText(oOutput, sNewPrefix, iPrefixLength + 4 + iNameLength);
 838        }
 839        CATCH_THROW_SIMPLE_(delete[] sNewPrefix, L"Cannot write child table \'%S\'", sName);
 840        delete[] sNewPrefix;
 841      }
 842    }
 843  }
 844}
 845
 846#undef LITERAL
 847
 848void LuaAttrib::loadFromFile(IFile *pFile) throw(...)
 849{
 850  m_oGameData.free();
 851  lua_State *L = getCache()->getLuaState();
 852  lua_checkstack(L, 1);
 853  if(pFile->lua_load(L, "attrib file") != 0)
 854  {
 855    // Try and parse the Lua error into a nice exception
 856    // Lua gives an error as "file:line:message", which can be reparsed into an exception
 857    size_t iLength;
 858    const char* sErr = lua_tolstring(L, -1, &iLength);
 859    if(sErr)
 860    {
 861      const char* sLineBegin = strchr(sErr, ':');
 862      if(sLineBegin)
 863      {
 864        ++sLineBegin;
 865        const char* sMessageBegin = strchr(sLineBegin, ':');
 866        if(sMessageBegin)
 867        {
 868          ++sMessageBegin;
 869          throw new RainException(__FILE__, __LINE__, RainString(L"Cannot parse attrib file"), new
 870            RainException(RainString(sErr, sLineBegin - sErr - 1), atoi(sLineBegin), RainString(sMessageBegin, iLength - (sMessageBegin - sErr))));
 871        }
 872      }
 873    }
 874
 875    // ... or just throw a simple exception
 876    throw new RainException(__FILE__, __LINE__, RainString(L"Cannot parse attrib file: ") + RainString(L, -1));
 877  }
 878  Proto *pPrototype = reinterpret_cast<Closure*>(const_cast<void*>(lua_topointer(L, -1)))->l.p;
 879  _execute(pPrototype);
 880}
 881
 882void LuaAttrib::_loadk(_value_t *pDestination, Proto* pFunction, int iK)
 883{
 884  pDestination->free();
 885  TValue *pValue = pFunction->k + iK;
 886  switch(pValue->tt)
 887  {
 888  case LUA_TNIL:
 889    break;
 890  case LUA_TBOOLEAN:
 891    pDestination->eType = _value_t::T_Boolean;
 892    pDestination->bValue = pValue->value.b != 0;
 893    break;
 894  case LUA_TNUMBER:
 895    pDestination->eType = _value_t::T_Float;
 896    pDestination->fValue = static_cast<float>(pValue->value.n);
 897    break;
 898  case LUA_TSTRING:
 899    pDestination->eType = _value_t::T_String;
 900    pDestination->sValue = svalue(pValue);
 901    pDestination->iLength = static_cast<long>(tsvalue(pValue)->len);
 902    break;
 903  default:
 904    THROW_SIMPLE_(L"Unsupported constant type: %i", static_cast<int>(pValue->tt));
 905  };
 906}
 907
 908unsigned long LuaAttrib::_hashvalue(_value_t* pValue)
 909{
 910  switch(pValue->eType)
 911  {
 912  case _value_t::T_String:
 913    return RgdDictionary::getSingleton()->asciiToHash(pValue->sValue, pValue->iLength);
 914  case _value_t::T_Boolean:
 915    return RgdDictionary::getSingleton()->asciiToHash(pValue->bValue ? "true" : "false");
 916  case _value_t::T_Float: {
 917    char sBuffer[32];
 918    if(floor(pValue->fValue) == pValue->fValue)
 919      sprintf(sBuffer, "%.0f", pValue->fValue);
 920    else
 921      sprintf(sBuffer, "%.7f", pValue->fValue);
 922    return RgdDictionary::getSingleton()->asciiToHash(sBuffer, strlen(sBuffer)); }
 923  default:
 924    THROW_SIMPLE_("Cannot hash value of type %i", static_cast<int>(pValue->eType));
 925  }
 926}
 927
 928void LuaAttrib::_wraptable(_value_t* pValue) throw(...)
 929{
 930  _table_t* pTable = pValue->pValue;
 931  pValue->pValue = CHECK_ALLOCATION(new (std::nothrow) _table_t(this));
 932  pValue->pValue->pInheritFrom = pTable;
 933}
 934
 935template <class T>
 936struct AutoDeleteArray
 937{
 938  AutoDeleteArray(T* p) : m_p(p) {}
 939  ~AutoDeleteArray() {delete[] m_p;}
 940  T* m_p;
 941};
 942
 943void LuaAttrib::_execute(Proto* pFunction)
 944{
 945  _value_t oTempK1, oTempK2;
 946  _value_t* pStack = CHECK_ALLOCATION(new (std::nothrow) _value_t[pFunction->maxstacksize]);
 947  Instruction *I;
 948  AutoDeleteArray<_value_t> DeleteStack(pStack);
 949
 950  bool bContinue = true;
 951  try
 952  {
 953    for(I = pFunction->code; bContinue; ++I)
 954    {
 955      switch(GET_OPCODE(*I))
 956      {
 957      case OP_MOVE:      // A B   R(A) := R(B)
 958        pStack[GETARG_A(*I)] = pStack[GETARG_B(*I)];
 959        break;
 960
 961      case OP_LOADK:     // A Bx  R(A) := Kst(Bx)
 962        _loadk(pStack + GETARG_A(*I), pFunction, GETARG_Bx(*I));
 963        break;
 964
 965      case OP_LOADBOOL:  // A B C R(A) := (Bool)B; if (C) pc++
 966        pStack[GETARG_A(*I)].free();
 967        pStack[GETARG_A(*I)].eType = _value_t::T_Boolean;
 968        pStack[GETARG_A(*I)].bValue = GETARG_B(*I) != 0;
 969        if(GETARG_C(*I))
 970          ++I;
 971        break;
 972
 973      case OP_LOADNIL:   // A B   R(A) := ... := R(B) := nil
 974        for(int i = GETARG_A(*I); i <= GETARG_B(*I); ++i)
 975          pStack[i].free();
 976        break;
 977
 978      case OP_GETGLOBAL:{// A Bx  R(A) := Gbl[Kst(Bx)]
 979        unsigned int iHash = tsvalue(pFunction->k + GETARG_Bx(*I))->hash;
 980        const char* s = svalue(pFunction->k + GETARG_Bx(*I));
 981        switch(iHash)
 982        {
 983        case LuaCompileTimeHashes::GameData:
 984          pStack[GETARG_A(*I)] = m_oGameData;
 985          break;
 986        case LuaCompileTimeHashes::MetaData:
 987          pStack[GETARG_A(*I)] = m_oMetaData;
 988          break;
 989        case LuaCompileTimeHashes::Inherit:
 990        case LuaCompileTimeHashes::Reference:
 991          pStack[GETARG_A(*I)].free();
 992          pStack[GETARG_A(*I)].eType = _value_t::T_ReferenceFn;
 993          break;
 994        case LuaCompileTimeHashes::InheritMeta:
 995          pStack[GETARG_A(*I)].free();
 996          pStack[GETARG_A(*I)].eType = _value_t::T_InheritMetaFn;
 997          break;
 998        default:
 999          THROW_SIMPLE_(L"Unsupported global: %S", s);
1000        }
1001        break; }
1002
1003      case OP_GETTABLE: {// A B C R(A) := R(B)[RK(C)]
1004        _value_t* pTable = pStack + GETARG_B(*I);
1005        CHECK_ASSERT(pTable->eType == _value_t::T_Table && pTable->pValue && "Using a non-table value as a table");
1006        _value_t* pKey = ISK(GETARG_C(*I)) ? (_loadk(&oTempK1, pFunction, INDEXK(GETARG_C(*I))), &oTempK1) : pStack + GETARG_C(*I);
1007        unsigned long iHash = _hashvalue(pKey);
1008        _value_t oValue;
1009        _table_t* pLookin = pTable->pValue;
1010        while(pLookin && pLookin->mapContents.count(iHash) == 0)
1011          pLookin = const_cast<_table_t*>(pLookin->pInheritFrom);
1012        if(pLookin)
1013        {
1014          oValue = pLookin->mapContents[iHash];
1015          if(oValue.eType == _value_t::T_Table && pLookin != pTable->pValue)
1016          {
1017            _wraptable(&oValue);
1018            pTable->pValue->mapContents[iHash] = oValue;
1019          }
1020        }
1021        pStack[GETARG_A(*I)] = oValue;
1022        break; }
1023
1024      case OP_SETGLOBAL:{// A Bx  Gbl[Kst(Bx)] := R(A)
1025        unsigned int iHash = tsvalue(pFunction->k + GETARG_Bx(*I))->hash;
1026        if(iHash == LuaCompileTimeHashes::GameData)
1027          m_oGameData = pStack[GETARG_A(*I)];
1028        else if(iHash == LuaCompileTimeHashes::MetaData)
1029          m_oMetaData = pStack[GETARG_A(*I)];
1030        else
1031          THROW_SIMPLE_(L"Unsupported global for write access: %S", svalue(pFunction->k + GETARG_Bx(*I)));
1032        break; }
1033
1034      case OP_SETTABLE: {// A B C R(A)[RK(B)] := RK(C)
1035        _value_t* pTable = pStack + GETARG_A(*I);
1036        _value_t* pKey = ISK(GETARG_B(*I)) ? (_loadk(&oTempK2, pFunction, INDEXK(GETARG_B(*I))), &oTempK2) : pStack + GETARG_B(*I);
1037        _value_t* pValue = ISK(GETARG_C(*I)) ? (_loadk(&oTempK1, pFunction, INDEXK(GETARG_C(*I))), &oTempK1) : pStack + GETARG_C(*I);
1038        if(pTable->eType != _value_t::T_Table || !pTable->pValue)
1039        {
1040          THROW_SIMPLE_(L"Using a non-table value as a table: table['%S'] = '%S' on line %i",
1041            pKey->eType == _value_t::T_String ? pKey->sValue : "(non string)",
1042            pValue->eType == _value_t::T_String ? pValue->sValue : "(non string)",
1043            static_cast<int>(pFunction->sizelineinfo == pFunction->sizecode ? pFunction->lineinfo[I - pFunction->code] : -1));
1044        }
1045        unsigned long iHash = _hashvalue(pKey);
1046        pTable->pValue->mapContents[iHash] = *pValue;
1047        break; }
1048
1049      case OP_NEWTABLE:  // A B C R(A) := {} (size = B,C)
1050        pStack[GETARG_A(*I)].free();
1051        pStack[GETARG_A(*I)].eType = _value_t::T_Table;
1052        CHECK_ALLOCATION(pStack[GETARG_A(*I)].pValue = new (std::nothrow) _table_t(this));
1053        break;
1054
1055      case OP_NOT: {     // A B   R(A) := not R(B)
1056        bool bValue = false;
1057        _value_t* pValue = pStack + GETARG_B(*I);
1058        if(pValue->eType == _value_t::T_Nil)
1059          bValue = true;
1060        if(pValue->eType == _value_t::T_Boolean)
1061          bValue = !pValue->bValue;
1062        pValue = pStack + GETARG_A(*I);
1063        pValue->free();
1064        pValue->eType = _value_t::T_Boolean;
1065        pValue->bValue = bValue;
1066        break; }
1067
1068      case OP_JMP:       // sBx   pc+=sBx
1069        I += GETARG_sBx(*I);
1070        break;
1071
1072      case OP_CALL: {    // A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
1073        _value_t* pFunction = pStack + GETARG_A(*I);
1074        CHECK_ASSERT((pFunction->eType == _value_t::T_InheritMetaFn || pFunction->eType == _value_t::T_ReferenceFn) && "Attempt to call a non-function");
1075        CHECK_ASSERT(GETARG_B(*I) != 1 && "Function call requires at least one argument");
1076        CHECK_ASSERT(GETARG_C(*I) == 2 && "Function call must have exactly one result");
1077        _value_t* pFilename = pStack + GETARG_A(*I) + 1;
1078        CHECK_ASSERT(pFilename->eType == _value_t::T_String && "Can only inherit/reference a string filename");
1079        try
1080        {
1081          if(pFunction->eType == _value_t::T_InheritMetaFn)
1082            pStack[GETARG_A(*I)] = getCache()->getMetaData(pFilename->sValue);
1083          else
1084            pStack[GETARG_A(*I)] = getCache()->getGameData(pFilename->sValue);
1085          if(pStack[GETARG_A(*I)].eType != _value_t::T_Table)
1086            THROW_SIMPLE(L"Inheritance function did not return a table");
1087        }
1088        CATCH_THROW_SIMPLE_({}, L"Cannot load \'%S\'", pFilename->sValue);
1089        if(pStack[GETARG_A(*I)].eType == _value_t::T_Table)
1090        {
1091          _wraptable(pStack + GETARG_A(*I));
1092          pStack[GETARG_A(*I)].pValue->mapContents[RgdDictionary::_REF] = *pFilename;
1093          pStack[GETARG_A(*I)].pValue->pSourceFile = this;
1094        }
1095        break; }
1096
1097      case OP_RETURN:    // A B   return R(A), ... ,R(A+B-2)	(see note)
1098        bContinue = false;
1099        break;
1100
1101      default:
1102        THROW_SIMPLE_(L"Unsupported Lua Opcode: %i", static_cast<int>(GET_OPCODE(*I)));
1103      }
1104    }
1105  }
1106  catch(RainException *pE)
1107  {
1108    RainString sSource(L"Lua function");
1109    if(pFunction->source && getstr(pFunction->source))
1110      sSource = RainString(getstr(pFunction->source), pFunction->source->tsv.len);
1111    unsigned long iLine = 0;
1112    if(pFunction->lineinfo)
1113      iLine = pFunction->lineinfo[I - pFunction->code];
1114
1115    throw new RainException(sSource, iLine, L"Error while executing Lua in custom VM", pE);
1116  }
1117}
1118
1119LuaAttrib::_value_t::_value_t()
1120{
1121  eType = T_Nil;
1122}
1123
1124LuaAttrib::_value_t::_value_t(const _value_t& oOther)
1125{
1126  eType = T_Nil;
1127  *this = oOther;
1128}
1129
1130LuaAttrib::_value_t::~_value_t()
1131{
1132  free();
1133}
1134
1135LuaAttrib::_value_t& LuaAttrib::_value_t::operator =(const _value_t& oOther)
1136{
1137  if(oOther.eType == T_Table)
1138    ++oOther.pValue->iReferenceCount;
1139  free();
1140  memcpy(this, &oOther, sizeof(_value_t));
1141  return *this;
1142}
1143
1144void LuaAttrib::_value_t::free()
1145{
1146  if(eType == T_Table)
1147  {
1148    if(--pValue->iReferenceCount == 0)
1149      delete pValue;
1150  }
1151  eType = T_Nil;
1152}
1153
1154LuaAttrib::_table_t::_table_t(LuaAttrib* pFile)
1155{
1156  pSourceFile = pFile;
1157  iReferenceCount = 1;
1158  pInheritFrom = 0;
1159  bTotallyUnchaged = false;
1160}
1161
1162LuaAttrib::_table_t::~_table_t()
1163{
1164  if(pInheritFrom && --((_table_t*)pInheritFrom)->iReferenceCount == 0)
1165    delete pInheritFrom;
1166}
1167
1168size_t LuaAttrib::_table_t::writeToBinary(IFile* pFile) const
1169{
1170  // Generate a list of keys to write
1171  std::map<unsigned long, const _table_t*> mapKeys;
1172  for(const _table_t* pTable = this; pTable; pTable = pTable->pInheritFrom)
1173  {
1174    for(std::map<unsigned long, _value_t>::const_iterator itr = pTable->mapContents.begin(); itr != pTable->mapContents.end(); ++itr)
1175    {
1176      if(mapKeys.count(itr->first) == 0)
1177        mapKeys[itr->first] = pTable;
1178    }
1179  }
1180
1181  // Prepare a memory buffer for the header and leave space in the output to write it when done
1182  size_t iHeaderLength = mapKeys.size() * sizeof(long) * 3;
1183  MemoryWriteFile fTableHeader(iHeaderLength);
1184  pFile->writeArray(fTableHeader.getBuffer(), iHeaderLength);
1185
1186  // Write keys
1187  size_t iDataLength = 0;
1188  for(std::map<unsigned long, const _table_t*>::iterator itr = mapKeys.begin(); itr != mapKeys.end(); ++itr)
1189  {
1190    const _value_t& oValue = itr->second->mapContents.find(itr->first)->second;
1191    // Set the data type code and alignment length for the type being written
1192    bool bConvertStringToWide = false;
1193    long iDataTypeCode;
1194    size_t iAlignLength;
1195    switch(oValue.eType)
1196    {
1197    case LuaAttrib::_value_t::T_Float:
1198      iDataTypeCode = BinaryAttribDataTypeCode<float>::code;
1199      iAlignLength = sizeof(float);
1200      break;
1201
1202    case LuaAttrib::_value_t::T_String:
1203      iDataTypeCode = BinaryAttribDataTypeCode<char*>::code;
1204      // The the opportunity to check if the string needs to be converted to a unicode (wide) string
1205      // This is done for all strings of form "$[0-9]+"
1206      if(oValue.sValue[0] == '$' && oValue.iLength >= 2)
1207      {
1208        bConvertStringToWide = true;
1209        for(long i = 1; i < oValue.iLength; ++i)
1210        {
1211          if(oValue.sValue[i] < '0' || oValue.sValue[i] > '9')
1212          {
1213            bConvertStringToWide = false;
1214            break;
1215          }
1216        }
1217        if(bConvertStringToWide)
1218        {
1219          iDataTypeCode = BinaryAttribDataTypeCode<wchar_t*>::code;
1220          iAlignLength = sizeof(wchar_t);
1221        }
1222      }
1223      break;
1224
1225    case LuaAttrib::_value_t::T_Boolean:
1226      iDataTypeCode = BinaryAttribDataTypeCode<bool>::code;
1227      iAlignLength = sizeof(char);
1228      break;
1229
1230    case LuaAttrib::_value_t::T_Integer:
1231      iDataTypeCode = BinaryAttribDataTypeCode<long>::code;
1232      iAlignLength = sizeof(long);
1233      break;
1234
1235    case LuaAttrib::_value_t::T_Table:
1236      iDataTypeCode = BinaryAttribDataTypeCode<IAttributeTable>::code;
1237      iAlignLength = sizeof(long);
1238      break;
1239
1240    default:
1241      THROW_SIMPLE_(L"Cannot write type %li to binary file", static_cast<long>(oValue.eType));
1242    }
1243
1244    // Write any alignment bytes
1245    if(iDataLength % iAlignLength)
1246    {
1247#define max2(a, b) ((a) > (b) ? (a) : (b))
1248#define max4(a, b, c, d) max2( max2((a), (b)), max2((c), (d)) )
1249      unsigned char cNullBytes[max4(sizeof(long), sizeof(float), sizeof(char), sizeof(wchar_t))] = {0};
1250#undef max4
1251#undef max2
1252      iAlignLength = iAlignLength - (iDataLength % iAlignLength);
1253      iDataLength += iAlignLength;
1254      pFile->writeArray(cNullBytes, iAlignLength);
1255    }
1256
1257    fTableHeader.writeOne(itr->first);
1258    fTableHeader.writeOne(iDataTypeCode);
1259    fTableHeader.writeOne(static_cast<unsigned long>(iDataLength));
1260
1261    switch(oValue.eType)
1262    {
1263    case LuaAttrib::_value_t::T_Float:
1264      pFile->writeOne(oValue.fValue);
1265      iDataLength += sizeof(float);
1266      break;
1267
1268    case LuaAttrib::_value_t::T_String:
1269      if(bConvertStringToWide)
1270      {
1271        wchar_t c;
1272        for(long i = 0; i <= oValue.iLength; ++i)
1273        {
1274          c = oValue.sValue[i];
1275          pFile->writeOne(c);
1276        }
1277        iDataLength += oValue.iLength * sizeof(wchar_t);
1278      }
1279      else
1280      {
1281        pFile->writeArray(oValue.sValue, oValue.iLength + 1);
1282        iDataLength += oValue.iLength + 1;
1283      }
1284      break;
1285
1286    case LuaAttrib::_value_t::T_Boolean: {
1287      pFile->writeOne(oValue.bValue ? BinaryAttribBooleanEncoding<true>::value : BinaryAttribBooleanEncoding<false>::value);
1288      iDataLength += sizeof BinaryAttribBooleanEncoding<true>::value;
1289      break; }
1290
1291    case LuaAttrib::_value_t::T_Integer:
1292      pFile->writeOne(oValue.iValue);
1293      iDataLength += sizeof(long);
1294      break;
1295
1296    case LuaAttrib::_value_t::T_Table:
1297      iDataLength += pSourceFile->getCache()->writeTableToBinaryFile(oValue.pValue, pFile, itr->second != this);
1298      break;
1299    };
1300  }
1301
1302  // Write table header
1303  pFile->seek(-static_cast<seek_offset_t>(iDataLength + iHeaderLength), SR_Current);
1304  pFile->writeArray(fTableHeader.getBuffer(), iHeaderLength);
1305  pFile->seek(-static_cast<seek_offset_t>(iDataLength), SR_Current);
1306
1307  return iDataLength + iHeaderLength;
1308}
1309
1310#endif