// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . /* This class is case unsensitive. It means that you can call build() and * buildIdVector() with string with anycase, it'll work. */ #include "stdmisc.h" #include "nel/misc/file.h" #include "nel/misc/path.h" #include "nel/misc/sheet_id.h" #include "nel/misc/common.h" #include "nel/misc/hierarchical_timer.h" using namespace std; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NLMISC { CSheetId::CChar CSheetId::_AllStrings; CStaticMap CSheetId::_SheetIdToName; CStaticMap CSheetId::_SheetNameToId; //map CSheetId::_SheetIdToName; //map CSheetId::_SheetNameToId; vector CSheetId::_FileExtensions; bool CSheetId::_Initialised=false; bool CSheetId::_RemoveUnknownSheet=true; bool CSheetId::_DontHaveSheetKnowledge = false; std::map CSheetId::_DevTypeNameToId; std::vector > CSheetId::_DevSheetIdToName; std::map CSheetId::_DevSheetNameToId; #define NL_TEMP_YUBO_NO_SOUND_SHEET_ID #ifdef NL_TEMP_YUBO_NO_SOUND_SHEET_ID namespace { bool a_NoSoundSheetId = false; const uint32 a_NoSoundSheetType = 80; } #endif const CSheetId CSheetId::Unknown(0); void CSheetId::cbFileChange (const std::string &filename) { nlinfo ("SHEETID: %s changed, reload it", filename.c_str()); loadSheetId(); } //----------------------------------------------- // CSheetId // //----------------------------------------------- CSheetId::CSheetId( uint32 sheetRef) { _Id.Id = sheetRef; #ifdef NL_DEBUG_SHEET_ID // Yoyo: don't access the static map, because of order of static ctor call. // For now, all static CSheetId are 0 (eg: CSheetId::Unknown) if(sheetRef) { CStaticMap::iterator it(_SheetIdToName.find(sheetRef)); if (it != _SheetIdToName.end()) { _DebugSheetName = it->second.Ptr; } else _DebugSheetName = NULL; } else { _DebugSheetName = NULL; } #endif } //----------------------------------------------- // CSheetId // //----------------------------------------------- CSheetId::CSheetId( const string& sheetName ) { if (!buildSheetId(sheetName)) { if(sheetName.empty()) nlwarning("SHEETID: Try to create an CSheetId with empty name. TODO: check why."); else nlwarning("SHEETID: The sheet '%s' is not in sheet_id.bin, setting it to Unknown",sheetName.c_str()); //std::string stack; //NLMISC::getCallStack(stack); //std::vector contexts; //NLMISC::explode(stack, string("\n"), contexts); //nldebug("Dumping callstack :"); //for (uint i=0; i::iterator it = _DevSheetNameToId.find(sheetNameLc); if (it == _DevSheetNameToId.end()) { // Create a new dynamic sheet ID. // nldebug("SHEETID: Creating a dynamic sheet id for '%s'", sheetName.c_str()); std::string sheetType = CFile::getExtension(sheetNameLc); std::string sheetName = CFile::getFilenameWithoutExtension(sheetNameLc); std::map::iterator tit = _DevTypeNameToId.find(sheetType); uint32 typeId; if (tit == _DevTypeNameToId.end()) { _FileExtensions.push_back(sheetType); _DevSheetIdToName.push_back(std::vector()); typeId = (uint32)_FileExtensions.size() - 1; _DevTypeNameToId[sheetType] = typeId; std::string unknownNewType = std::string("unknown." + sheetType); _DevSheetIdToName[typeId].push_back(unknownNewType); _Id.IdInfos.Type = typeId; _Id.IdInfos.Id = _DevSheetIdToName[typeId].size() - 1; _DevSheetNameToId[unknownNewType] = _Id.Id; if (sheetName == "unknown") return true; // Return with the unknown sheet id of this type } else { typeId = tit->second; _Id.IdInfos.Type = typeId; } // Add a new sheet name to the type _DevSheetIdToName[typeId].push_back(sheetNameLc); _Id.IdInfos.Id = _DevSheetIdToName[typeId].size() - 1; // nldebug("SHEETID: Type %i, id %i, sheetid %i", _Id.IdInfos.Type, _Id.IdInfos.Id, _Id.Id); _DevSheetNameToId[sheetNameLc] = _Id.Id; return true; } _Id.Id = it->second; return true; } // try looking up the sheet name in _SheetNameToId CStaticMap::const_iterator itId; CChar c; c.Ptr = new char [sheetName.size()+1]; strcpy(c.Ptr, sheetName.c_str()); toLower(c.Ptr); itId = _SheetNameToId.find (c); delete [] c.Ptr; if( itId != _SheetNameToId.end() ) { _Id.Id = itId->second; #ifdef NL_DEBUG_SHEET_ID // store debug info _DebugSheetName = itId->first.Ptr; #endif return true; } // we failed to find the sheet name in the sheetname map so see if the string is numeric if (sheetName.size()>1 && sheetName[0]=='#') { uint32 numericId; NLMISC::fromString((const char*)(sheetName.c_str()+1), numericId); if (NLMISC::toString("#%u",numericId)==sheetName) { _Id.Id= numericId; return true; } } #ifdef NL_TEMP_YUBO_NO_SOUND_SHEET_ID if (a_NoSoundSheetId && sheetName.find(".sound") != std::string::npos) { std::string sheetNameLc = toLower(sheetName); std::map::iterator it = _DevSheetNameToId.find(sheetNameLc); if (it == _DevSheetNameToId.end()) { // nldebug("SHEETID: Creating a temporary sheet id for '%s'", sheetName.c_str()); _DevSheetIdToName[0].push_back(sheetName); _Id.IdInfos.Type = a_NoSoundSheetType; _Id.IdInfos.Id = _DevSheetIdToName[0].size() - 1; _DevSheetNameToId[sheetNameLc] = _Id.Id; return true; } _Id.Id = it->second; return true; } #endif return false; } void CSheetId::loadSheetId () { H_AUTO(CSheetIdInit); //nldebug("Loading sheet_id.bin"); // Open the sheet id to sheet file name association CIFile file; std::string path = CPath::lookup("sheet_id.bin", false, false); if(!path.empty() && file.open(path)) { // clear entries _FileExtensions.clear (); _SheetIdToName.clear (); _SheetNameToId.clear (); // reserve space for the vector of file extensions _FileExtensions.resize(1 << (NL_SHEET_ID_TYPE_BITS)); // Get the map from the file map tempMap; contReset(tempMap); file.serialCont(tempMap); file.close(); if (_RemoveUnknownSheet) { uint32 removednbfiles = 0; uint32 nbfiles = (uint32)tempMap.size(); // now we remove all files that not available map::iterator itStr2; for( itStr2 = tempMap.begin(); itStr2 != tempMap.end(); ) { if (CPath::exists ((*itStr2).second)) { ++itStr2; } else { map::iterator olditStr = itStr2; //nldebug ("Removing file '%s' from CSheetId because the file not exists", (*olditStr).second.c_str ()); itStr2++; tempMap.erase (olditStr); removednbfiles++; } } nlinfo ("SHEETID: Removed %d files on %d from CSheetId because these files don't exist", removednbfiles, nbfiles); } // Convert the map to one big string and 1 static map (id to name) { // Get the number and size of all strings vector tempVec; // Used to initialise the first map uint32 nNb = 0; uint32 nSize = 0; map::const_iterator it = tempMap.begin(); while (it != tempMap.end()) { nSize += (uint32)it->second.size()+1; nNb++; it++; } // Make the big string (composed of all strings) and a vector referencing each string tempVec.resize(nNb); _AllStrings.Ptr = new char[nSize]; it = tempMap.begin(); nSize = 0; nNb = 0; while (it != tempMap.end()) { tempVec[nNb].Ptr = _AllStrings.Ptr+nSize; strcpy(_AllStrings.Ptr+nSize, it->second.c_str()); toLower(_AllStrings.Ptr+nSize); nSize += (uint32)it->second.size()+1; nNb++; it++; } // Finally build the static map (id to name) _SheetIdToName.reserve(tempVec.size()); it = tempMap.begin(); nNb = 0; while (it != tempMap.end()) { _SheetIdToName.add(pair(it->first, CChar(tempVec[nNb]))); nNb++; it++; } // The vector of all small string is not needed anymore we have all the info in // the static map and with the pointer AllStrings referencing the beginning. } // Build the invert map (Name to Id) & file extension vector { uint32 nSize = (uint32)_SheetIdToName.size(); _SheetNameToId.reserve(nSize); CStaticMap::iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { // add entry to the inverse map _SheetNameToId.add( make_pair((*itStr).second, (*itStr).first) ); // work out the type value for this entry in the map TSheetId sheetId; sheetId.Id=(*itStr).first; uint32 type = sheetId.IdInfos.Type; // check whether we need to add an entry to the file extensions vector if (_FileExtensions[type].empty()) { // find the file extension part of the given file name _FileExtensions[type] = toLower(CFile::getExtension((*itStr).second.Ptr)); } nSize--; } _SheetNameToId.endAdd(); } } else { nlerror(" Can't open the file sheet_id.bin"); } nldebug("Finished loading sheet_id.bin: %u entries read",_SheetIdToName.size()); } //----------------------------------------------- // init // //----------------------------------------------- void CSheetId::init(bool removeUnknownSheet) { // allow multiple calls to init in case libraries depending on sheetid call this init from their own if (_Initialised) { if (_DontHaveSheetKnowledge) nlinfo("SHEETID: CSheetId is already initialized without sheet_id.bin"); return; } // CFile::addFileChangeCallback ("sheet_id.bin", cbFileChange); _RemoveUnknownSheet = removeUnknownSheet; loadSheetId (); _Initialised=true; #ifdef NL_TEMP_YUBO_NO_SOUND_SHEET_ID if (typeFromFileExtension("sound") == std::numeric_limits::max()) { nlwarning("SHEETID: Loading without known sound sheet id, please update sheet_id.bin with .sound sheets"); nlassert(_FileExtensions.size() == 1 << (NL_SHEET_ID_TYPE_BITS)); nlassert(_FileExtensions[a_NoSoundSheetType].empty()); _FileExtensions[a_NoSoundSheetType] = "sound"; _DevSheetIdToName.push_back(std::vector()); _DevSheetIdToName[0].push_back("unknown.sound"); TSheetId id; id.IdInfos.Type = a_NoSoundSheetType; id.IdInfos.Id = _DevSheetIdToName[0].size() - 1; nlassert(id.IdInfos.Id == 0); _DevSheetNameToId["unknown.sound"] = id.Id; a_NoSoundSheetId = true; } #endif } // init // void CSheetId::initWithoutSheet() { if (_Initialised) { nlassert(_DontHaveSheetKnowledge); return; } _Initialised = true; _DontHaveSheetKnowledge = true; // Initialize id 0,0 as unknown.unknown CSheetId unknownunknown = CSheetId("unknown.unknown"); nlassert(unknownunknown == CSheetId::Unknown); } //----------------------------------------------- // uninit // //----------------------------------------------- void CSheetId::uninit() { delete [] _AllStrings.Ptr; _FileExtensions.clear(); _DevTypeNameToId.clear(); _DevSheetIdToName.clear(); _DevSheetNameToId.clear(); } // uninit // //----------------------------------------------- // operator= // //----------------------------------------------- CSheetId& CSheetId::operator=( const CSheetId& sheetId ) { if (!_Initialised) init(false); if(this == &sheetId) { return *this; } _Id.Id = sheetId.asInt(); #ifdef NL_DEBUG_SHEET_ID _DebugSheetName = sheetId._DebugSheetName; #endif return *this; } // operator= // //----------------------------------------------- // operator= // //----------------------------------------------- CSheetId& CSheetId::operator=( const string& sheetName ) { if (!buildSheetId(sheetName)) *this = Unknown; // nldebug("LIST_SHEET_ID: %s (%s)", toString().c_str(), sheetName.c_str()); return *this; } // operator= // //----------------------------------------------- // operator= // //----------------------------------------------- CSheetId& CSheetId::operator=( uint32 sheetRef ) { if (!_Initialised) init(false); _Id.Id = sheetRef; return *this; } // operator= // //----------------------------------------------- // operator< // //----------------------------------------------- bool CSheetId::operator < (const CSheetId& sheetRef ) const { if (!_Initialised) init(false); if (_Id.Id < sheetRef.asInt()) { return true; } return false; } // operator< // //----------------------------------------------- // toString // //----------------------------------------------- string CSheetId::toString(bool ifNotFoundUseNumericId) const { if (!_Initialised) init(false); if (_DontHaveSheetKnowledge) { // FIXME: When someone punches in a fake sheet id this will // fail. return _DevSheetIdToName[_Id.IdInfos.Type][_Id.IdInfos.Id]; } CStaticMap::const_iterator itStr = _SheetIdToName.find (_Id.Id); if( itStr != _SheetIdToName.end() ) { return string((*itStr).second.Ptr); } else { #ifdef NL_TEMP_YUBO_NO_SOUND_SHEET_ID if (a_NoSoundSheetId && _Id.IdInfos.Type == a_NoSoundSheetType) { return _DevSheetIdToName[0][_Id.IdInfos.Id]; } #endif // This nlwarning is commented out because the loggers are mutexed, therefore // you couldn't use toString() within a nlwarning(). //nlwarning(" The sheet %08x is not in sheet_id.bin",_Id.Id); if (ifNotFoundUseNumericId) { return NLMISC::toString( "#%u", _Id.Id ); } else { return NLMISC::toString( "", _Id.Id ); } } } // toString // void CSheetId::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { nlassert(!_DontHaveSheetKnowledge); f.serial( _Id.Id ); #ifdef NL_DEBUG_SHEET_ID CStaticMap::iterator it(_SheetIdToName.find(_Id.Id)); if (it != _SheetIdToName.end()) _DebugSheetName = it->second.Ptr; else _DebugSheetName = NULL; #endif } void CSheetId::serialString(NLMISC::IStream &f, const std::string &defaultType) throw(NLMISC::EStream) { nlassert(_Initialised); if (f.isReading()) { std::string sheetName; f.serial(sheetName); *this = CSheetId(sheetName, defaultType); } else { // if this assert fails, you may be using an outdated id bin nlassert(*this != CSheetId::Unknown); std::string sheetName = toString(); f.serial(sheetName); } } //----------------------------------------------- // display // //----------------------------------------------- void CSheetId::display() { if (!_Initialised) init(false); CStaticMap::const_iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { //nlinfo("%d %s",(*itStr).first,(*itStr).second.c_str()); nlinfo("SHEETID: (%08x %d) %s",(*itStr).first,(*itStr).first,(*itStr).second.Ptr); } } // display // //----------------------------------------------- // display // //----------------------------------------------- void CSheetId::display(uint32 type) { if (!_Initialised) init(false); CStaticMap::const_iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { // work out the type value for this entry in the map TSheetId sheetId; sheetId.Id=(*itStr).first; // decide whether or not to display the entry if (type==sheetId.IdInfos.Type) { //nlinfo("%d %s",(*itStr).first,(*itStr).second.c_str()); nlinfo("SHEETID: (%08x %d) %s",(*itStr).first,(*itStr).first,(*itStr).second.Ptr); } } } // display // //----------------------------------------------- // buildIdVector // //----------------------------------------------- void CSheetId::buildIdVector(std::vector &result) { if (!_Initialised) init(false); CStaticMap::const_iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { result.push_back( (CSheetId)(*itStr).first ); } } // buildIdVector // //----------------------------------------------- // buildIdVector // //----------------------------------------------- void CSheetId::buildIdVector(std::vector &result, uint32 type) { if (!_Initialised) init(false); nlassert(type < (1 << (NL_SHEET_ID_TYPE_BITS))); CStaticMap::const_iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { // work out the type value for this entry in the map TSheetId sheetId; sheetId.Id=(*itStr).first; // decide whether or not to use the entry if (type==sheetId.IdInfos.Type) { result.push_back( (CSheetId)sheetId.Id ); } } } // buildIdVector // //----------------------------------------------- // buildIdVector // //----------------------------------------------- void CSheetId::buildIdVector(std::vector &result, std::vector &resultFilenames,uint32 type) { if (!_Initialised) init(false); nlassert(type < (1 << (NL_SHEET_ID_TYPE_BITS))); CStaticMap::const_iterator itStr; for( itStr = _SheetIdToName.begin(); itStr != _SheetIdToName.end(); ++itStr ) { // work out the type value for this entry in the map TSheetId sheetId; sheetId.Id=(*itStr).first; // decide whether or not to use the entry if (type==sheetId.IdInfos.Type) { result.push_back( (CSheetId)sheetId.Id ); resultFilenames.push_back( (*itStr).second.Ptr ); } } } // buildIdVector // //----------------------------------------------- // buildIdVector // //----------------------------------------------- void CSheetId::buildIdVector(std::vector &result,const std::string &fileExtension) { uint32 type=typeFromFileExtension(fileExtension); if (type != std::numeric_limits::max()) buildIdVector(result, type); } // buildIdVector // //----------------------------------------------- // buildIdVector // //----------------------------------------------- void CSheetId::buildIdVector(std::vector &result, std::vector &resultFilenames,const std::string &fileExtension) { uint32 type=typeFromFileExtension(fileExtension); if (type != std::numeric_limits::max()) buildIdVector(result,resultFilenames, type); } // buildIdVector // //----------------------------------------------- // typeFromFileExtension // //----------------------------------------------- uint32 CSheetId::typeFromFileExtension(const std::string &fileExtension) { if (!_Initialised) init(false); uint i; for (i=0;i<_FileExtensions.size();i++) if (toLower(fileExtension)==_FileExtensions[i]) return i; return std::numeric_limits::max(); } // typeFromFileExtension // //----------------------------------------------- // fileExtensionFromType // //----------------------------------------------- const std::string &CSheetId::fileExtensionFromType(uint32 type) { if (!_Initialised) init(false); nlassert(type < (1<<(NL_SHEET_ID_TYPE_BITS))); return _FileExtensions[type]; } // fileExtensionFromType // //----------------------------------------------- // build // //----------------------------------------------- void CSheetId::buildSheetId(uint32 shortId, uint32 type) { nlassert(shortId < (1<::iterator it(_SheetIdToName.find(_Id.Id)); if (it != _SheetIdToName.end()) { _DebugSheetName = it->second.Ptr; } else _DebugSheetName = NULL; #endif } } // NLMISC