From 8040a81eec40b739786641134b2acaa3d0180618 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 7 Aug 2012 11:28:52 +0000 Subject: [PATCH] - fixed save games issues (#1044) - hopefully some speedup for filesystem loading (#1048) --- client/AdventureMapClasses.cpp | 5 +- client/CPlayerInterface.cpp | 4 +- client/CPreGame.cpp | 12 +-- client/NetPacksClient.cpp | 8 +- config/filesystem.json | 23 ++--- lib/CStopWatch.h | 2 +- lib/Filesystem/CFileInfo.cpp | 49 +-------- lib/Filesystem/CFileInfo.h | 12 --- lib/Filesystem/CFilesystemLoader.cpp | 96 ++++++++++------- lib/Filesystem/CFilesystemLoader.h | 48 ++++----- lib/Filesystem/CLodArchiveLoader.cpp | 26 +---- lib/Filesystem/CLodArchiveLoader.h | 43 +------- lib/Filesystem/CResourceLoader.cpp | 136 ++++++++++++++++--------- lib/Filesystem/CResourceLoader.h | 41 +++++--- lib/Filesystem/ISimpleResourceLoader.h | 13 ++- lib/VCMI_Lib.cpp | 17 +++- server/CGameHandler.cpp | 16 ++- 17 files changed, 268 insertions(+), 283 deletions(-) diff --git a/client/AdventureMapClasses.cpp b/client/AdventureMapClasses.cpp index f9fdfc2b6..e789fb173 100644 --- a/client/AdventureMapClasses.cpp +++ b/client/AdventureMapClasses.cpp @@ -537,7 +537,10 @@ void CMinimap::moveAdvMapSelection() int3 newLocation = translateMousePosition(); adventureInt->centerOn(newLocation); - GH.totalRedraw(); //redraw this as well as adventure map (which may be inactive) + if (!(adventureInt->active & GENERAL)) + GH.totalRedraw(); //redraw this as well as inactive adventure map + else + redraw();//redraw only this } void CMinimap::clickLeft(tribool down, bool previousState) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index b30d06d4b..77d6d95f5 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -165,13 +165,13 @@ void CPlayerInterface::yourTurn() { int index = getLastIndex("Newgame_Autosave_"); index %= SAVES_COUNT; - cb->save("Newgame_Autosave_" + boost::lexical_cast(index + 1)); + cb->save("Saves/Newgame_Autosave_" + boost::lexical_cast(index + 1)); } firstCall = 0; } else { - LOCPLINT->cb->save("Autosave_" + boost::lexical_cast(autosaveCount++ + 1)); + LOCPLINT->cb->save("Saves/Autosave_" + boost::lexical_cast(autosaveCount++ + 1)); autosaveCount %= 5; } diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index e7b3e7da8..10509d8fe 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -843,13 +843,13 @@ void CSelectionScreen::startGame() if(!(sel && sel->txt && sel->txt->text.size())) return; - selectedName = GVCMIDirs.UserPath + "/Games/" + sel->txt->text + ".vlgm1"; + selectedName = "Saves/" + sel->txt->text; CFunctionList overWrite; - overWrite += boost::bind(&CCallback::save, LOCPLINT->cb, sel->txt->text); + overWrite += boost::bind(&CCallback::save, LOCPLINT->cb, selectedName); overWrite += bind(&CGuiHandler::popIntTotally, &GH, this); - if(fs::exists(selectedName)) + if(CResourceHandler::get()->existsResource(ResourceID(selectedName, EResType::LIB_SAVEGAME))) { std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite? boost::algorithm::replace_first(hlp, "%s", sel->txt->text); @@ -857,9 +857,6 @@ void CSelectionScreen::startGame() } else overWrite(); - -// LOCPLINT->cb->save(sel->txt->text); -// GH.popIntTotally(this); } } @@ -1417,7 +1414,8 @@ void SelectionTab::printMaps(SDL_Surface *to) } else { - name = fs::basename(currentItem->fileURI); + name = CFileInfo(CResourceHandler::get()->getResourceName( + ResourceID(currentItem->fileURI, EResType::LIB_SAVEGAME))).getBaseName(); } //print name diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 7373a7102..1f9195f79 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -1,6 +1,8 @@ #include "StdInc.h" #include "../lib/NetPacks.h" +#include "../lib/Filesystem/CResourceLoader.h" +#include "../lib/Filesystem/CFileInfo.h" #include "../CCallback.h" #include "Client.h" #include "CPlayerInterface.h" @@ -759,9 +761,13 @@ void YourTurn::applyCl( CClient *cl ) void SaveGame::applyCl(CClient *cl) { + tlog1 << "Saving to " << fname << "\n"; + CFileInfo info(fname); + CResourceHandler::get()->createResource(info.getStem() + ".vcgm1"); + try { - CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vcgm1"); + CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::CLIENT_SAVEGAME))); save << *cl; } catch(std::exception &e) diff --git a/config/filesystem.json b/config/filesystem.json index afb388b0d..5ec63e5d9 100644 --- a/config/filesystem.json +++ b/config/filesystem.json @@ -13,15 +13,13 @@ [ {"type" : "file", "path" : "ALL/Data/H3ab_bmp.lod"}, {"type" : "file", "path" : "ALL/Data/H3bitmap.lod"}, - {"type" : "dir", "path" : "GLOBAL/Data"}, - {"type" : "dir", "path" : "LOCAL/Data"} + {"type" : "dir", "path" : "ALL/Data"} ], "SPRITES/": [ {"type" : "file", "path" : "ALL/Data/H3ab_spr.lod"}, {"type" : "file", "path" : "ALL/Data/H3sprite.lod"}, - {"type" : "dir", "path" : "GLOBAL/Sprites"}, - {"type" : "dir", "path" : "LOCAL/Sprites"} + {"type" : "dir", "path" : "ALL/Sprites"} ], "SOUNDS/": [ @@ -29,35 +27,30 @@ {"type" : "file", "path" : "ALL/Data/Heroes3-cd2.snd"}, {"type" : "file", "path" : "ALL/Data/Heroes3.snd"}, //WoG have overriden sounds with .82m extension in Data - {"type" : "dir", "path" : "GLOBAL/Data"}, - {"type" : "dir", "path" : "LOCAL/Data"} + {"type" : "dir", "path" : "ALL/Data"} ], "MUSIC/": [ - {"type" : "dir", "path" : "GLOBAL/Mp3"}, - {"type" : "dir", "path" : "LOCAL/Mp3"} + {"type" : "dir", "path" : "ALL/Mp3"} ], "VIDEO/": [ {"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"}, {"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"}, // Location of video files in linux release - {"type" : "dir", "path" : "GLOBAL/Data/Video"}, - {"type" : "dir", "path" : "LOCAL/Data/Video"} + {"type" : "dir", "path" : "ALL/Data/Video"} ], "CONFIG/": [ - {"type" : "dir", "path" : "GLOBAL/Config"}, - {"type" : "dir", "path" : "LOCAL/Config"} + {"type" : "dir", "path" : "ALL/Config", "writeable": true} ], "MAPS/": [ - {"type" : "dir", "path" : "GLOBAL/Maps"}, - {"type" : "dir", "path" : "LOCAL/Maps"} + {"type" : "dir", "path" : "ALL/Maps"} ], "SAVES/": [ - {"type" : "dir", "path" : "LOCAL/Games"}, + {"type" : "dir", "path" : "LOCAL/Games", "writeable": true}, ] } } diff --git a/lib/CStopWatch.h b/lib/CStopWatch.h index 009d354fb..201a0e697 100644 --- a/lib/CStopWatch.h +++ b/lib/CStopWatch.h @@ -48,7 +48,7 @@ public: } si64 memDif() { - return clock()-mem; + return (clock()-mem) / TO_MS_DIVISOR; } private: diff --git a/lib/Filesystem/CFileInfo.cpp b/lib/Filesystem/CFileInfo.cpp index 26e219ccd..79876c34a 100644 --- a/lib/Filesystem/CFileInfo.cpp +++ b/lib/Filesystem/CFileInfo.cpp @@ -70,16 +70,18 @@ std::string CFileInfo::getStem() const std::string CFileInfo::getBaseName() const { - size_t end = name.find_last_of("/."); size_t begin = name.find_last_of("/"); + size_t end = name.find_last_of("/."); - if(end != std::string::npos && name[end] == '.') + if(end != std::string::npos && name[end] == '/') end = std::string::npos; if(begin == std::string::npos) begin = 0; + else + begin++; - return name.substr(begin, end); + return name.substr(begin, end - begin); } @@ -92,44 +94,3 @@ std::time_t CFileInfo::getDate() const { return boost::filesystem::last_write_time(name); } - -std::unique_ptr > CFileInfo::listFiles(size_t depth, const std::string & extensionFilter /*= ""*/) const -{ - std::unique_ptr > fileListPtr; - - if(exists() && isDirectory()) - { - std::list * fileList = new std::list; - - std::vector path; - - boost::filesystem::recursive_directory_iterator enddir; - boost::filesystem::recursive_directory_iterator it(name, boost::filesystem::symlink_option::recurse); - - for(; it != enddir; ++it) - { - if (boost::filesystem::is_directory(it->status())) - { - path.resize(it.level()+1); - path.back() = it->path().leaf().string(); - it.no_push(depth <= it.level()); - } - - if(extensionFilter.empty() || it->path().extension() == extensionFilter) - { - std::string filename; - for (size_t i=0; ipath().leaf().string(); - - //tlog1 << "Found file: " << filename << "\n"; - CFileInfo file(filename); - fileList->push_back(file); - } - } - - fileListPtr.reset(fileList); - } - - return fileListPtr; -} diff --git a/lib/Filesystem/CFileInfo.h b/lib/Filesystem/CFileInfo.h index 4817e3c49..14128450e 100644 --- a/lib/Filesystem/CFileInfo.h +++ b/lib/Filesystem/CFileInfo.h @@ -108,18 +108,6 @@ public: */ std::time_t getDate() const; - /** - * Returns a list of pathnames denoting the files in the directory denoted by this pathname. - * - * If the pathname of this directory is absolute, then the file info pathnames are absolute as well. If the pathname of this directory is relative - * then the file info pathnames are relative to the basedir as well. - * - * @param extensionFilter Filters files by the given extension. Optional. Empty string if all files and directories in the directory should be listed. - * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname as a unique ptr. - * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory. - */ - std::unique_ptr > listFiles(size_t depth, const std::string & extensionFilter = "") const; - private: /** Contains the original URI(not modified) e.g. ./dir/foo.txt */ std::string name; diff --git a/lib/Filesystem/CFilesystemLoader.cpp b/lib/Filesystem/CFilesystemLoader.cpp index 18c59f530..11b51f012 100644 --- a/lib/Filesystem/CFilesystemLoader.cpp +++ b/lib/Filesystem/CFilesystemLoader.cpp @@ -4,31 +4,10 @@ #include "CFileInfo.h" #include "CFileInputStream.h" -CFilesystemLoader::CFilesystemLoader() +CFilesystemLoader::CFilesystemLoader(const std::string & baseDirectory, size_t depth, bool initial): + baseDirectory(baseDirectory), + fileList(listFiles(depth, initial)) { - -} - -CFilesystemLoader::CFilesystemLoader(const std::string & baseDirectory, size_t depth) -{ - open(baseDirectory, depth); -} - -CFilesystemLoader::CFilesystemLoader(const CFileInfo & baseDirectory, size_t depth) -{ - open(baseDirectory.getName(), depth); -} - -void CFilesystemLoader::open(const std::string & baseDirectory, size_t depth) -{ - // Indexes all files in the directory and store them - this->baseDirectory = baseDirectory; - CFileInfo directory(baseDirectory); - std::unique_ptr > fileList = directory.listFiles(depth); - if(fileList) - { - this->fileList = std::move(*fileList); - } } std::unique_ptr CFilesystemLoader::load(const std::string & resourceName) const @@ -41,7 +20,7 @@ bool CFilesystemLoader::existsEntry(const std::string & resourceName) const { for(auto it = fileList.begin(); it != fileList.end(); ++it) { - if(it->getName() == resourceName) + if(it->second == resourceName) { return true; } @@ -50,19 +29,68 @@ bool CFilesystemLoader::existsEntry(const std::string & resourceName) const return false; } -std::list CFilesystemLoader::getEntries() const +std::unordered_map CFilesystemLoader::getEntries() const { - std::list retList; - - for(auto it = fileList.begin(); it != fileList.end(); ++it) - { - retList.push_back(it->getName()); - } - - return std::move(retList); + return fileList; } std::string CFilesystemLoader::getOrigin() const { return baseDirectory; } + +bool CFilesystemLoader::createEntry(std::string filename) +{ + ResourceID res(filename); + if (fileList.find(res) != fileList.end()) + return false; + + std::ofstream file(baseDirectory + '/' + filename); + if (!file.good()) + return false; + + fileList[res] = filename; + return true; +} + + +std::unordered_map CFilesystemLoader::listFiles(size_t depth, bool initial) const +{ + + assert(boost::filesystem::is_directory(baseDirectory)); + std::unordered_map fileList; + + std::vector path;//vector holding relative path to our file + + boost::filesystem::recursive_directory_iterator enddir; + boost::filesystem::recursive_directory_iterator it(baseDirectory, boost::filesystem::symlink_option::recurse); + + for(; it != enddir; ++it) + { + EResType::Type type; + + if (boost::filesystem::is_directory(it->status())) + { + path.resize(it.level()+1); + path.back() = it->path().leaf().string(); + it.no_push(depth <= it.level()); // don't iterate into directory if depth limit reached + + type = EResType::DIRECTORY; + } + else + type = EResTypeHelper::getTypeFromExtension(boost::filesystem::extension(*it)); + + if (!initial || type == EResType::DIRECTORY || type == EResType::ARCHIVE || type == EResType::TEXT) + { + //reconstruct relative filename (not possible via boost AFAIK) + std::string filename; + for (size_t i=0; ipath().leaf().string(); + + fileList[ResourceID(filename, type)] = filename; + } + } + + return fileList; +} \ No newline at end of file diff --git a/lib/Filesystem/CFilesystemLoader.h b/lib/Filesystem/CFilesystemLoader.h index 9acafad6e..42bfafa3a 100644 --- a/lib/Filesystem/CFilesystemLoader.h +++ b/lib/Filesystem/CFilesystemLoader.h @@ -12,6 +12,7 @@ #pragma once #include "ISimpleResourceLoader.h" +#include "CResourceLoader.h" class CFileInfo; class CInputStream; @@ -22,11 +23,6 @@ class CInputStream; class DLL_LINKAGE CFilesystemLoader : public ISimpleResourceLoader { public: - /** - * Default c-tor. - */ - CFilesystemLoader(); - /** * Ctor. * @@ -35,25 +31,7 @@ public: * * @throws std::runtime_error if the base directory is not a directory or if it is not available */ - explicit CFilesystemLoader(const std::string & baseDirectory, size_t depth = 16); - - /** - * Ctor. - * - * @param baseDirectory Specifies the base directory and their sub-directories which should be indexed. - * - * @throws std::runtime_error if the base directory is not a directory or if it is not available - */ - explicit CFilesystemLoader(const CFileInfo & baseDirectory, size_t depth = 16); - - /** - * Opens a base directory to be read and indexed. - * - * @param baseDirectory Specifies the base directory and their sub-directories which should be indexed. - * - * @throws std::runtime_error if the base directory is not a directory or if it is not available - */ - void open(const std::string & baseDirectory, size_t depth); + explicit CFilesystemLoader(const std::string & baseDirectory, size_t depth = 16, bool initial = false); /** * Loads a resource with the given resource name. @@ -75,7 +53,7 @@ public: * * @return a list of all entries in the filesystem. */ - std::list getEntries() const; + std::unordered_map getEntries() const; /** * Gets the origin of the archive loader. @@ -84,10 +62,26 @@ public: */ std::string getOrigin() const; + bool createEntry(std::string filename); + private: /** The base directory which is scanned and indexed. */ std::string baseDirectory; - /** A list of files in the directory */ - std::list fileList; + /** A list of files in the directory + * key = ResourceID for resource loader + * value = name that can be used to access file + */ + std::unordered_map fileList; + + /** + * Returns a list of pathnames denoting the files in the directory denoted by this pathname. + * + * If the pathname of this directory is absolute, then the file info pathnames are absolute as well. If the pathname of this directory is relative + * then the file info pathnames are relative to the basedir as well. + * + * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname + * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory. + */ + std::unordered_map listFiles(size_t depth, bool initial) const; }; diff --git a/lib/Filesystem/CLodArchiveLoader.cpp b/lib/Filesystem/CLodArchiveLoader.cpp index dc95bd3fc..3e3374236 100644 --- a/lib/Filesystem/CLodArchiveLoader.cpp +++ b/lib/Filesystem/CLodArchiveLoader.cpp @@ -13,22 +13,7 @@ ArchiveEntry::ArchiveEntry() } -CLodArchiveLoader::CLodArchiveLoader() -{ - -} - CLodArchiveLoader::CLodArchiveLoader(const std::string & archive) -{ - open(archive); -} - -CLodArchiveLoader::CLodArchiveLoader(const CFileInfo & archive) -{ - open(archive); -} - -void CLodArchiveLoader::open(const std::string & archive) { // Open archive file(.snd, .vid, .lod) this->archive = archive; @@ -58,11 +43,6 @@ void CLodArchiveLoader::open(const std::string & archive) } } -void CLodArchiveLoader::open(const CFileInfo & archive) -{ - open(archive.getName()); -} - void CLodArchiveLoader::initLODArchive(CFileInputStream & fileStream) { // Define LodEntryBlock struct @@ -204,14 +184,14 @@ std::unique_ptr CLodArchiveLoader::load(const std::string & resour } } -std::list CLodArchiveLoader::getEntries() const +std::unordered_map CLodArchiveLoader::getEntries() const { - std::list retList; + std::unordered_map retList; for(auto it = entries.begin(); it != entries.end(); ++it) { const ArchiveEntry & entry = it->second; - retList.push_back(entry.name); + retList[ResourceID(entry.name)] = entry.name; } return std::move(retList); diff --git a/lib/Filesystem/CLodArchiveLoader.h b/lib/Filesystem/CLodArchiveLoader.h index f7d60cd53..29e0d3e1a 100644 --- a/lib/Filesystem/CLodArchiveLoader.h +++ b/lib/Filesystem/CLodArchiveLoader.h @@ -45,11 +45,6 @@ struct ArchiveEntry class DLL_LINKAGE CLodArchiveLoader : public ISimpleResourceLoader { public: - /** - * Default c-tor. - */ - CLodArchiveLoader(); - /** * Ctor. * @@ -62,42 +57,6 @@ public: */ explicit CLodArchiveLoader(const std::string & archive); - /** - * Ctor. - * - * The file extension of the param archive determines the type of the Lod file. - * These are valid extensions: .LOD, .SND, .VID - * - * @param archive Specifies the file path to the archive which should be indexed and loaded. - * - * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported - */ - explicit CLodArchiveLoader(const CFileInfo & archive); - - /** - * Opens an LOD archive. - * - * The file extension of the param archive determines the type of the Lod file. - * These are valid extensions: .LOD, .SND, .VID - * - * @param archive Specifies the file path to the archive which should be indexed and loaded. - * - * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported - */ - void open(const std::string & archive); - - /** - * Opens an LOD archive. - * - * The file extension of the param archive determines the type of the Lod file. - * These are valid extensions: .LOD, .SND, .VID - * - * @param archive Specifies the file path to the archive which should be indexed and loaded. - * - * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported - */ - void open(const CFileInfo & archive); - /** * Loads a resource with the given resource name. * @@ -113,7 +72,7 @@ public: * * @return a list of all entries in the archive. */ - std::list getEntries() const; + std::unordered_map getEntries() const; /** * Gets the archive entry for the requested resource diff --git a/lib/Filesystem/CResourceLoader.cpp b/lib/Filesystem/CResourceLoader.cpp index fd4f0b7d7..fa2601675 100644 --- a/lib/Filesystem/CResourceLoader.cpp +++ b/lib/Filesystem/CResourceLoader.cpp @@ -9,9 +9,6 @@ #include "../GameConstants.h" #include "../VCMIDirs.h" -//experimental support for ERA-style mods. Requires custom config in mod directory -#define ENABLE_ERA_FILESYSTEM - CResourceLoader * CResourceHandler::resourceLoader = nullptr; CResourceLoader * CResourceHandler::initialLoader = nullptr; @@ -35,9 +32,14 @@ ResourceID::ResourceID(const std::string & name, EResType::Type type) ResourceID::ResourceID(const std::string & prefix, const std::string & name, EResType::Type type) { - setName(name); - this->name = prefix + this->name; + this->name = name; + size_t dotPos = this->name.find_last_of("/."); + + if(dotPos != std::string::npos && this->name[dotPos] == '.') + this->name.erase(dotPos); + + this->name = prefix + this->name; setType(type); } @@ -73,15 +75,6 @@ CResourceLoader::CResourceLoader() { } -CResourceLoader::~CResourceLoader() -{ - // Delete all loader objects - BOOST_FOREACH ( auto & it, loaders) - { - delete it; - } -} - std::unique_ptr CResourceLoader::load(const ResourceID & resourceIdent) const { auto resource = resources.find(resourceIdent); @@ -139,31 +132,46 @@ std::string CResourceLoader::getResourceName(const ResourceID & resourceIdent) c bool CResourceLoader::existsResource(const ResourceID & resourceIdent) const { - // Check if resource is registered return resources.find(resourceIdent) != resources.end(); } -void CResourceLoader::addLoader(std::string mountPoint, ISimpleResourceLoader * loader) +bool CResourceLoader::createResource(std::string URI) { - loaders.insert(loader); + std::string filename = URI; + boost::to_upper(URI); + BOOST_REVERSE_FOREACH (const LoaderEntry & entry, loaders) + { + if (entry.writeable && boost::algorithm::starts_with(URI, entry.prefix)) + { + // remove loader prefix from filename + filename = filename.substr(entry.prefix.size()); + if (!entry.loader->createEntry(filename)) + return false; //or continue loop? + + resources[ResourceID(URI)].push_back(ResourceLocator(entry.loader.get(), filename)); + } + } + return false; +} + +void CResourceLoader::addLoader(std::string mountPoint, shared_ptr loader, bool writeable) +{ + LoaderEntry loaderEntry; + loaderEntry.loader = loader; + loaderEntry.prefix = mountPoint; + loaderEntry.writeable = writeable; + loaders.push_back(loaderEntry); // Get entries and add them to the resources list - const std::list & entries = loader->getEntries(); + const std::unordered_map & entries = loader->getEntries(); boost::to_upper(mountPoint); - BOOST_FOREACH (const std::string & entry, entries) + BOOST_FOREACH (auto & entry, entries) { - CFileInfo file(entry); - // Create identifier and locator and add them to the resources list - ResourceID ident(mountPoint, file.getStem(), file.getType()); - - //check if entry can be directory. Will only work for filesystem loader but H3 archives don't have dirs anyway. - if (boost::filesystem::is_directory(loader->getOrigin() + '/' + entry)) - ident.setType(EResType::DIRECTORY); - - ResourceLocator locator(loader, entry); + ResourceID ident(mountPoint, entry.first.getName(), entry.first.getType()); + ResourceLocator locator(loader.get(), entry.second); resources[ident].push_back(locator); } } @@ -178,7 +186,7 @@ CResourceLoader * CResourceHandler::get() { std::stringstream string; string << "Error: Resource loader wasn't initialized. " - << "Make sure that you set one via CResourceLoaderFactory::setInstance"; + << "Make sure that you set one via CResourceLoaderFactory::initialize"; throw std::runtime_error(string.str()); } } @@ -234,6 +242,7 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) (".PAC", EResType::ARCHIVE) (".VID", EResType::ARCHIVE) (".SND", EResType::ARCHIVE) + (".PAL", EResType::PALETTE) (".VCGM1", EResType::CLIENT_SAVEGAME) (".VLGM1", EResType::LIB_SAVEGAME) (".VSGM1", EResType::SERVER_SAVEGAME); @@ -260,6 +269,7 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type) MAP_ENUM(SOUND) MAP_ENUM(MUSIC) MAP_ENUM(ARCHIVE) + MAP_ENUM(PALETTE) MAP_ENUM(CLIENT_SAVEGAME) MAP_ENUM(LIB_SAVEGAME) MAP_ENUM(SERVER_SAVEGAME) @@ -276,26 +286,45 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type) void CResourceHandler::initialize() { + //recurse only into specific directories + auto recurseInDir = [](std::string URI, int depth) + { + auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY)); + BOOST_FOREACH(const ResourceLocator & entry, resources) + { + std::string filename = entry.getLoader()->getOrigin() + '/' + entry.getResourceName(); + if (!filename.empty()) + { + shared_ptr dir(new CFilesystemLoader(filename, depth, true)); + initialLoader->addLoader(URI + '/', dir, false); + } + } + }; + //temporary filesystem that will be used to initialize main one. //used to solve several case-sensivity issues like Mp3 vs MP3 initialLoader = new CResourceLoader; resourceLoader = new CResourceLoader; - auto rootDir = new CFilesystemLoader(GameConstants::DATA_DIR, 3); - initialLoader->addLoader("GLOBAL/", rootDir); - initialLoader->addLoader("ALL/", rootDir); + shared_ptr rootDir(new CFilesystemLoader(GameConstants::DATA_DIR, 0, true)); + initialLoader->addLoader("GLOBAL/", rootDir, false); + initialLoader->addLoader("ALL/", rootDir, false); auto userDir = rootDir; //add local directory to "ALL" but only if it differs from root dir (true for linux) if (GameConstants::DATA_DIR != GVCMIDirs.UserPath) { - userDir = new CFilesystemLoader(GVCMIDirs.UserPath, 3); - initialLoader->addLoader("ALL/", userDir); + userDir = shared_ptr(new CFilesystemLoader(GVCMIDirs.UserPath, 0, true)); + initialLoader->addLoader("ALL/", userDir, false); } //create "LOCAL" dir with current userDir (may be same as rootDir) - initialLoader->addLoader("LOCAL/", userDir); + initialLoader->addLoader("LOCAL/", userDir, false); + + recurseInDir("ALL/CONFIG", 0);// look for configs + recurseInDir("ALL/DATA", 0); // look for archives + recurseInDir("ALL/MODS", 2); // look for mods. Depth 2 is required for now but won't cause issues if no mods present } void CResourceHandler::loadFileSystem(const std::string fsConfigURI) @@ -310,23 +339,30 @@ void CResourceHandler::loadFileSystem(const std::string fsConfigURI) { tlog5 << "loading resource at " << entry["path"].String() << "\n"; + std::string URI = entry["path"].String(); if (entry["type"].String() == "dir") { - std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::DIRECTORY)); - if (!filename.empty()) + bool writeable = entry["writeable"].Bool(); + int depth = 16; + if (!entry["depth"].isNull()) + depth = entry["depth"].Float(); + + auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY)); + + BOOST_FOREACH(const ResourceLocator & entry, resources) { - int depth = 16; - if (!entry["depth"].isNull()) - depth = entry["depth"].Float(); - resourceLoader->addLoader(mountPoint.first, new CFilesystemLoader(filename, depth)); + std::string filename = entry.getLoader()->getOrigin() + '/' + entry.getResourceName(); + resourceLoader->addLoader(mountPoint.first, + shared_ptr(new CFilesystemLoader(filename, depth)), writeable); } } if (entry["type"].String() == "file") { - std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::ARCHIVE)); + std::string filename = initialLoader->getResourceName(ResourceID(URI, EResType::ARCHIVE)); if (!filename.empty()) - resourceLoader->addLoader(mountPoint.first, new CLodArchiveLoader(filename)); + resourceLoader->addLoader(mountPoint.first, + shared_ptr(new CLodArchiveLoader(filename)), false); } } } @@ -334,7 +370,6 @@ void CResourceHandler::loadFileSystem(const std::string fsConfigURI) void CResourceHandler::loadModsFilesystems() { -#ifdef ENABLE_ERA_FILESYSTEM auto iterator = initialLoader->getIterator([](const ResourceID & ident) -> bool { @@ -346,11 +381,18 @@ void CResourceHandler::loadModsFilesystems() && boost::algorithm::ends_with(name, "FILESYSTEM"); }); + //sorted storage for found mods + //implements basic load order (entries in hashtable are basically random) + std::set foundMods; while (iterator.hasNext()) { - tlog1 << "Found mod filesystem: " << iterator->getName() << "\n"; - loadFileSystem(iterator->getName()); + foundMods.insert(iterator->getName()); ++iterator; } -#endif + + BOOST_FOREACH(const std::string & entry, foundMods) + { + tlog1 << "\t\tFound mod filesystem: " << entry << "\n"; + loadFileSystem(entry); + } } diff --git a/lib/Filesystem/CResourceLoader.h b/lib/Filesystem/CResourceLoader.h index 3d49d3084..39f75cbba 100644 --- a/lib/Filesystem/CResourceLoader.h +++ b/lib/Filesystem/CResourceLoader.h @@ -33,6 +33,7 @@ class ISimpleResourceLoader; * Video: .smk, .bik .mjpg * Music: .mp3, .ogg * Archive: .lod, .snd, .vid .pac + * Palette: .pal * Savegame: .v*gm1 */ namespace EResType @@ -50,6 +51,7 @@ namespace EResType SOUND, MUSIC, ARCHIVE, + PALETTE, CLIENT_SAVEGAME, LIB_SAVEGAME, SERVER_SAVEGAME, @@ -128,7 +130,7 @@ protected: * Ctor for usage strictly in resourceLoader for some speedup * * @param prefix Prefix of ths filename, already in upper case - * @param name The resource name. + * @param name The resource name, upper case * @param type The resource type. A constant from the enumeration EResType. */ ResourceID(const std::string & prefix, const std::string & name, EResType::Type type); @@ -164,14 +166,14 @@ public: { return hash()(resourceIdent.getName()) ^ hash()(static_cast(resourceIdent.getType())); } - }; + }; }; /** * This class manages the loading of resources whether standard * or derived from several container formats and the file system. */ -class DLL_LINKAGE CResourceLoader : public boost::noncopyable +class DLL_LINKAGE CResourceLoader { typedef std::unordered_map > ResourcesMap; @@ -236,11 +238,6 @@ public: CResourceLoader(); - /** - * D-tor. - */ - virtual ~CResourceLoader(); - /** * Loads the resource specified by the resource identifier. * @@ -290,6 +287,16 @@ public: */ bool existsResource(const ResourceID & resourceIdent) const; + /** + * Creates new resource (if not exists) with specified URI. + * Type will be determined from extension + * File case will be same as in URI + * + * @param URI file to create + * @return true on success, false if resource exists or on error + */ + bool createResource(std::string URI); + /** * Adds a simple resource loader to the loaders list and its entries to the resources list. * @@ -300,7 +307,7 @@ public: * @param mountPoint prefix that will be added to all files in this loader * @param loader The simple resource loader object to add */ - void addLoader(std::string mountPoint, ISimpleResourceLoader * loader); + void addLoader(std::string mountPoint, shared_ptr loader, bool writeable); private: @@ -310,19 +317,21 @@ private: */ ResourcesMap resources; + struct LoaderEntry + { + std::string prefix; + shared_ptr loader; + bool writeable; + }; + /** A list of resource loader objects */ - std::set loaders; + std::vector loaders; }; /** * This class has static methods for a global resource loader access. * - * Note: Compared to the singleton pattern it has the advantage that the class CResourceLoader - * and classes which use it can be tested separately. CResourceLoader can be sub-classes as well. - * Compared to a global variable the factory pattern can throw an exception if the resource loader wasn't - * initialized. - * - * This class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling setInstance. + * Class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling initialize. */ class DLL_LINKAGE CResourceHandler { diff --git a/lib/Filesystem/ISimpleResourceLoader.h b/lib/Filesystem/ISimpleResourceLoader.h index 87f750b99..3d9bf9884 100644 --- a/lib/Filesystem/ISimpleResourceLoader.h +++ b/lib/Filesystem/ISimpleResourceLoader.h @@ -12,6 +12,7 @@ #pragma once #include "CInputStream.h" +#include "CResourceLoader.h" //FIXME: move ResourceID + EResType in separate file? /** * A class which knows the files containing in the archive or system and how to load them. @@ -44,7 +45,7 @@ public: * * @return Returns a list of all entries in the archive or (file) system. */ - virtual std::list getEntries() const =0; + virtual std::unordered_map getEntries() const =0; /** * Gets the origin of the loader. @@ -52,4 +53,14 @@ public: * @return the file path to source of this loader */ virtual std::string getOrigin() const =0; + + /** + * Creates new resource with specified filename. + * + * @returns true if new file was created, false on error or if file already exists + */ + virtual bool createEntry(std::string filename) + { + return false; + } }; diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 6b0cd516e..0ca32bbc5 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -160,12 +160,19 @@ DLL_LINKAGE void loadToIt(si32 &dest, const std::string &src, int &iter, int mod void LibClasses::loadFilesystem() { - CStopWatch pomtime; - CResourceHandler::initialize(); - CResourceHandler::loadFileSystem("ALL/config/filesystem.json"); - CResourceHandler::loadModsFilesystems(); + CStopWatch totalTime; + CStopWatch loadTime; - tlog0<<"\tFile system handler: "<tid)->tempOwner); } -void CGameHandler::save( const std::string &fname ) +void CGameHandler::save(const std::string & filename ) { + tlog1 << "Saving to " << filename << "\n"; + CFileInfo info(filename); + CResourceHandler::get()->createResource(info.getStem() + ".vlgm1"); + CResourceHandler::get()->createResource(info.getStem() + ".vsgm1"); + { tlog0 << "Ordering clients to serialize...\n"; - SaveGame sg(fname); - + SaveGame sg(info.getStem() + ".vcgm1"); sendToAllClients(&sg); } @@ -2260,14 +2266,14 @@ void CGameHandler::save( const std::string &fname ) { { tlog0 << "Serializing game info...\n"; - CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vlgm1"); + CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::LIB_SAVEGAME))); char hlp[8] = "VCMISVG"; save << hlp << static_cast(*gs->map) << gs->scenarioOps << *VLC << gs; } { tlog0 << "Serializing server info...\n"; - CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vsgm1"); + CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::SERVER_SAVEGAME))); save << *this; } tlog0 << "Game has been successfully saved!\n";