diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index fed3765825..9d73872d05 100644 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -26,6 +26,7 @@ #include "lib/res/graphics/ogl_tex.h" #include "lib/timer.h" #include "maths/MD5.h" +#include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" @@ -70,8 +71,9 @@ class CTextureManagerImpl { friend class CTexture; public: - CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) - : m_VFS(vfs), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), m_DefaultHandle(0), m_ErrorHandle(0) + CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) : + m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), + m_DefaultHandle(0), m_ErrorHandle(0) { // Initialise some textures that will always be available, // without needing to load any files @@ -220,47 +222,18 @@ public: } /** - * Determines whether we can safely use the archived cache file, or need to - * re-convert the source file. + * Set up some parameters for the loose cache filename code. */ - bool CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath) + void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version) { - // We want to use the archive cache whenever possible, - // unless it's superseded by a source file that the user has edited + // Hash the settings, so we won't use an old loose cache file if the + // settings have changed + CTextureConverter::Settings settings = GetConverterSettings(texture); + settings.Hash(hash); - size_t sourcePriority = 0; - size_t archiveCachePriority = 0; - - bool sourceExists = (m_VFS->GetFilePriority(sourcePath, &sourcePriority) >= 0); - bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0); - - // Can't use it if there's no cache - if (!archiveCacheExists) - return false; - - // Must use the cache if there's no source - if (!sourceExists) - return true; - - // If source file is from a higher-priority mod than archive cache, - // don't use the old cache - if (archiveCachePriority < sourcePriority) - return false; - - // If source file is more recent than the archive cache (i.e. the user has edited it), - // don't use the old cache - FileInfo sourceInfo, archiveCacheInfo; - if (m_VFS->GetFileInfo(sourcePath, &sourceInfo) >= 0 && - m_VFS->GetFileInfo(archiveCachePath, &archiveCacheInfo) >= 0) - { - const double howMuchNewer = difftime(sourceInfo.MTime(), archiveCacheInfo.MTime()); - const double threshold = 2.0; // FAT timestamp resolution [seconds] - if (howMuchNewer > threshold) - return false; - } - - // Otherwise we can use the cache - return true; + // Arbitrary version number - change this if we update the code and + // need to invalidate old users' caches + version = 1; } /** @@ -270,91 +243,33 @@ public: */ bool TryLoadingCached(const CTexturePtr& texture) { - VfsPath sourcePath = texture->m_Properties.m_Path; - VfsPath sourceDir = sourcePath.branch_path(); - std::wstring sourceName = sourcePath.leaf(); - VfsPath archiveCachePath = sourceDir / (sourceName + L".cached.dds"); + MD5 hash; + u32 version; + PrepareCacheKey(texture, hash, version); - // Try the archive cache file first - if (CanUseArchiveCache(sourcePath, archiveCachePath)) + VfsPath loadPath; + LibError ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath); + + if (ret == INFO::OK) { - LoadTexture(texture, archiveCachePath); + // Found a cached texture - load it + LoadTexture(texture, loadPath); return true; } - - // Fail if no source or archive cache - if (m_VFS->GetFileInfo(sourcePath, NULL) < 0) + else if (ret == INFO::SKIPPED) { - LOGERROR(L"Texture failed to find source file: \"%ls\"", texture->m_Properties.m_Path.string().c_str()); + // No cached version was found - we'll need to create it + return false; + } + else + { + debug_assert(ret < 0); + // No source file or archive cache was found, so we can't load the + // real texture at all - return the error texture instead texture->SetHandle(m_ErrorHandle); return true; } - - // Look for loose cache of source file - - VfsPath looseCachePath = LooseCachePath(texture); - - // If the loose cache file exists, use it - if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0) - { - LoadTexture(texture, looseCachePath); - return true; - } - - // No cache - we'll need to regenerate it - - return false; - } - - /** - * Returns the pathname for storing a loose cache file, based on the size/mtime of - * the source file and the conversion settings. The source file must already exist. - * - * TODO: this code should probably be shared with other cached data (XMB files etc). - */ - VfsPath LooseCachePath(const CTexturePtr& texture) - { - VfsPath sourcePath = texture->m_Properties.m_Path; - - FileInfo fileInfo; - if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0) - { - debug_warn(L"source file disappeared"); // this should never happen - return VfsPath(); - } - - u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it - u64 size = (u64)fileInfo.Size(); - - u32 version = 1; // change this if we update the code and need to invalidate old users' caches - - // Construct a hash of the file data and settings. - - CTextureConverter::Settings settings = GetConverterSettings(texture); - - MD5 hash; - hash.Update((const u8*)&mtime, sizeof(mtime)); - hash.Update((const u8*)&size, sizeof(size)); - hash.Update((const u8*)&version, sizeof(version)); - settings.Hash(hash); - // these are local cached files, so we don't care about endianness etc - - // Use a short prefix of the full hash (we don't need high collision-resistance), - // converted to hex - u8 digest[MD5::DIGESTSIZE]; - hash.Final(digest); - std::wstringstream digestPrefix; - digestPrefix << std::hex; - for (size_t i = 0; i < 8; ++i) - digestPrefix << std::setfill(L'0') << std::setw(2) << (int)digest[i]; - - // Construct the final path - VfsPath sourceDir = sourcePath.branch_path(); - std::wstring sourceName = sourcePath.leaf(); - return L"cache" / sourceDir / (sourceName + L"." + digestPrefix.str() + L".dds"); - - // TODO: we should probably include the mod name, once that's possible (http://trac.wildfiregames.com/ticket/564) } /** @@ -364,7 +279,11 @@ public: void ConvertTexture(const CTexturePtr& texture) { VfsPath sourcePath = texture->m_Properties.m_Path; - VfsPath looseCachePath = LooseCachePath(texture); + + MD5 hash; + u32 version; + PrepareCacheKey(texture, hash, version); + VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version); // LOGWARNING(L"Converting texture \"%ls\"", srcPath.string().c_str()); @@ -375,17 +294,12 @@ public: bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath) { - VfsPath sourceDir = sourcePath.branch_path(); - std::wstring sourceName = sourcePath.leaf(); - archiveCachePath = sourceDir / (sourceName + L".cached.dds"); + archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath); CTextureProperties textureProps(sourcePath); CTexturePtr texture = CreateTexture(textureProps); CTextureConverter::Settings settings = GetConverterSettings(texture); - // TODO: we ought to use a higher-quality compression mode here - // (since we don't care about performance) - if (!m_TextureConverter.ConvertTexture(texture, sourcePath, L"cache"/archiveCachePath, settings)) return false; @@ -537,7 +451,7 @@ public: m_SettingsFiles.erase(path); // Find all textures using this file - std::map > >::iterator files = m_HotloadFiles.find(path); + HotloadFilesMap::iterator files = m_HotloadFiles.find(path); if (files != m_HotloadFiles.end()) { // Flag all textures using this file as needing reloading @@ -556,6 +470,7 @@ public: private: PIVFS m_VFS; + CCacheLoader m_CacheLoader; bool m_DisableGL; CTextureConverter m_TextureConverter; @@ -570,7 +485,8 @@ private: // Store the set of textures that need to be reloaded when the given file // (a source file or settings.xml) is modified - std::map > > m_HotloadFiles; + typedef std::map > > HotloadFilesMap; + HotloadFilesMap m_HotloadFiles; // Cache for the conversion settings files typedef std::map > SettingsFilesMap; diff --git a/source/ps/ArchiveBuilder.cpp b/source/ps/ArchiveBuilder.cpp index fc30f55861..7db5e463ca 100644 --- a/source/ps/ArchiveBuilder.cpp +++ b/source/ps/ArchiveBuilder.cpp @@ -23,6 +23,7 @@ #include "lib/path_util.h" #include "lib/tex/tex_codec.h" #include "lib/file/archive/archive_zip.h" +#include "ps/XML/Xeromyces.h" // Disable "'boost::algorithm::detail::is_classifiedF' : assignment operator could not be generated" #if MSC_VERSION @@ -70,6 +71,8 @@ void CArchiveBuilder::Build(const fs::wpath& archive) // so it can deal with all the loading of settings.xml files CTextureManager texman(m_VFS, true, true); + CXeromyces xero; + for (size_t i = 0; i < m_Files.size(); ++i) { LibError ret; @@ -96,12 +99,30 @@ void CArchiveBuilder::Build(const fs::wpath& archive) debug_assert(ret == INFO::OK); writer->AddFile(cachedRealPath, cachedPath.string()); + + // We don't want to store the original file too (since it's a + // large waste of space), so skip to the next file + continue; } - // TODO: should cache XML->XMB and DAE->PMD and DAE->PSA conversions too - else + + // TODO: should cache DAE->PMD and DAE->PSA conversions too + + debug_printf(L"Adding %ls\n", realPath.string().c_str()); + writer->AddFile(realPath, m_Files[i].string()); + + // Also cache XMB versions of all XML files + if (fs::extension(m_Files[i]) == L".xml") { - debug_printf(L"Adding %ls\n", realPath.string().c_str()); - writer->AddFile(realPath, m_Files[i].string()); + VfsPath cachedPath; + debug_printf(L"Converting XML file %ls\n", realPath.string().c_str()); + bool ok = xero.GenerateCachedXMB(m_VFS, m_Files[i], cachedPath); + debug_assert(ok); + + fs::wpath cachedRealPath; + ret = m_VFS->GetRealPath(L"cache"/cachedPath, cachedRealPath); + debug_assert(ret == INFO::OK); + + writer->AddFile(cachedRealPath, cachedPath.string()); } } } diff --git a/source/ps/CacheLoader.cpp b/source/ps/CacheLoader.cpp new file mode 100644 index 0000000000..6fa1878ee8 --- /dev/null +++ b/source/ps/CacheLoader.cpp @@ -0,0 +1,149 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "CacheLoader.h" + +#include "ps/CLogger.h" +#include "maths/MD5.h" + +CCacheLoader::CCacheLoader(PIVFS vfs, const std::wstring& fileExtension) : + m_VFS(vfs), m_FileExtension(fileExtension) +{ +} + +LibError CCacheLoader::TryLoadingCached(const VfsPath& sourcePath, const MD5& initialHash, u32 version, VfsPath& loadPath) +{ + VfsPath archiveCachePath = ArchiveCachePath(sourcePath); + + // Try the archive cache file first + if (CanUseArchiveCache(sourcePath, archiveCachePath)) + { + loadPath = archiveCachePath; + return INFO::OK; + } + + // Fail if no source or archive cache + LibError err = m_VFS->GetFileInfo(sourcePath, NULL); + if (err < 0) + { + LOGERROR(L"Failed to find file: \"%ls\"", sourcePath.string().c_str()); + return err; + } + + // Look for loose cache of source file + + VfsPath looseCachePath = LooseCachePath(sourcePath, initialHash, version); + + // If the loose cache file exists, use it + if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0) + { + loadPath = looseCachePath; + return INFO::OK; + } + + // No cache - we'll need to regenerate it + + loadPath = looseCachePath; + return INFO::SKIPPED; +} + +bool CCacheLoader::CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath) +{ + // We want to use the archive cache whenever possible, + // unless it's superseded by a source file that the user has edited + + size_t sourcePriority = 0; + size_t archiveCachePriority = 0; + + bool sourceExists = (m_VFS->GetFilePriority(sourcePath, &sourcePriority) >= 0); + bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0); + + // Can't use it if there's no cache + if (!archiveCacheExists) + return false; + + // Must use the cache if there's no source + if (!sourceExists) + return true; + + // If source file is from a higher-priority mod than archive cache, + // don't use the old cache + if (archiveCachePriority < sourcePriority) + return false; + + // If source file is more recent than the archive cache (i.e. the user has edited it), + // don't use the old cache + FileInfo sourceInfo, archiveCacheInfo; + if (m_VFS->GetFileInfo(sourcePath, &sourceInfo) >= 0 && + m_VFS->GetFileInfo(archiveCachePath, &archiveCacheInfo) >= 0) + { + const double howMuchNewer = difftime(sourceInfo.MTime(), archiveCacheInfo.MTime()); + const double threshold = 2.0; // FAT timestamp resolution [seconds] + if (howMuchNewer > threshold) + return false; + } + + // Otherwise we can use the cache + return true; +} + +VfsPath CCacheLoader::ArchiveCachePath(const VfsPath& sourcePath) +{ + VfsPath sourceDir = sourcePath.branch_path(); + std::wstring sourceName = sourcePath.leaf(); + + return sourceDir / (sourceName + L".cached" + m_FileExtension); +} + +VfsPath CCacheLoader::LooseCachePath(const VfsPath& sourcePath, const MD5& initialHash, u32 version) +{ + FileInfo fileInfo; + if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0) + { + debug_warn(L"source file disappeared"); // this should never happen + return VfsPath(); + } + + u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it + u64 size = (u64)fileInfo.Size(); + + // Construct a hash of the file data and settings. + + MD5 hash = initialHash; + hash.Update((const u8*)&mtime, sizeof(mtime)); + hash.Update((const u8*)&size, sizeof(size)); + hash.Update((const u8*)&version, sizeof(version)); + // these are local cached files, so we don't care about endianness etc + + // Use a short prefix of the full hash (we don't need high collision-resistance), + // converted to hex + u8 digest[MD5::DIGESTSIZE]; + hash.Final(digest); + std::wstringstream digestPrefix; + digestPrefix << std::hex; + for (size_t i = 0; i < 8; ++i) + digestPrefix << std::setfill(L'0') << std::setw(2) << (int)digest[i]; + + // Construct the final path + VfsPath sourceDir = sourcePath.branch_path(); + std::wstring sourceName = sourcePath.leaf(); + return L"cache" / sourceDir / (sourceName + L"." + digestPrefix.str() + m_FileExtension); + + // TODO: we should probably include the mod name, once that's possible (http://trac.wildfiregames.com/ticket/564) +} diff --git a/source/ps/CacheLoader.h b/source/ps/CacheLoader.h new file mode 100644 index 0000000000..8219cf0b11 --- /dev/null +++ b/source/ps/CacheLoader.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CACHELOADER +#define INCLUDED_CACHELOADER + +#include "lib/file/vfs/vfs.h" + +class MD5; + +/** + * Helper class for systems that have an expensive cacheable conversion process + * when loading files. + * + * Conversion output can be automatically cached as loose files, indexed by a hash + * of the file's timestamp and size plus any other data the caller provides. + * This allows developers and modders to easily produce new files, with the conversion + * happening transparently. + * + * For release packages, files can be precached by appending ".cached.{extension}" + * to their name, which will be used instead of doing runtime conversion. + * These cache files will typically be packed into an archive for faster loading; + * if no archive cache is available then the source file will be converted and stored + * as a loose cache file instead. + */ +class CCacheLoader +{ +public: + CCacheLoader(PIVFS vfs, const std::wstring& fileExtension); + + /** + * Attempts to find a valid cached which can be loaded. + * Returns INFO::OK and sets loadPath to the cached file if there is one. + * Returns INFO::SKIPPED and sets loadPath to the desire loose cache name if there isn't one. + * Returns a value < 0 on error (e.g. the source file doesn't exist). + */ + LibError TryLoadingCached(const VfsPath& sourcePath, const MD5& initialHash, u32 version, VfsPath& loadPath); + + /** + * Determines whether we can safely use the archived cache file, or need to + * re-convert the source file. + */ + bool CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath); + + /** + * Return the path of the archive cache for the given source file. + */ + VfsPath ArchiveCachePath(const VfsPath& sourcePath); + + /** + * Return the path of the loose cache for the given source file. + */ + VfsPath LooseCachePath(const VfsPath& sourcePath, const MD5& initialHash, u32 version); + +private: + PIVFS m_VFS; + std::wstring m_FileExtension; +}; + +#endif // INCLUDED_CACHELOADER diff --git a/source/ps/XML/Xeromyces.cpp b/source/ps/XML/Xeromyces.cpp index 79cff783b3..c997103c4f 100644 --- a/source/ps/XML/Xeromyces.cpp +++ b/source/ps/XML/Xeromyces.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,14 +23,14 @@ #include #include +#include "maths/MD5.h" +#include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "Xeromyces.h" #include -#define LOG_CATEGORY L"xml" - static void errorHandler(void* UNUSED(userData), xmlErrorPtr error) { // Strip a trailing newline @@ -38,7 +38,7 @@ static void errorHandler(void* UNUSED(userData), xmlErrorPtr error) if (message.length() > 0 && message[message.length()-1] == '\n') message.erase(message.length()-1); - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Parse %ls: %hs:%d: %hs", + LOGERROR(L"CXeromyces: Parse %ls: %hs:%d: %hs", error->level == XML_ERR_WARNING ? L"warning" : L"error", error->file, error->line, message.c_str()); // TODO: The (non-fatal) warnings and errors don't get stored in the XMB, @@ -62,104 +62,71 @@ void CXeromyces::Terminate() g_XeromycesStarted = false; } - -// Find out write location of the XMB file corresponding to xmlFilename -void CXeromyces::GetXMBPath(const PIVFS& vfs, const VfsPath& xmlFilename, const VfsPath& xmbFilename, VfsPath& xmbActualPath) +void CXeromyces::PrepareCacheKey(MD5& hash, u32& version) { - // rationale: - // - it is necessary to write out XMB files into a subdirectory - // corresponding to the mod from which the XML file is taken. - // this avoids confusion when multiple mods are active - - // their XMB files' VFS filename would otherwise be indistinguishable. - // - we group files in the cache/ mount point first by mod, and only - // then XMB. this is so that all output files for a given mod can - // easily be deleted. the operation of deleting all old/unused - // XMB files requires a program anyway (to find out which are no - // longer needed), so it's not a problem that XMB files reside in - // a subdirectory (which would make manually deleting all harder). + // We don't have anything special to add into the hash + UNUSED2(hash); - // get real path of XML file (e.g. mods/official/entities/...) - fs::wpath XMBRealPath_; - vfs->GetRealPath(xmlFilename, XMBRealPath_); - wchar_t XMBRealPath[PATH_MAX]; - wcscpy_s(XMBRealPath, ARRAY_SIZE(XMBRealPath), XMBRealPath_.string().c_str()); - - // extract mod name from that - const wchar_t* modPath = wcsstr(XMBRealPath, L"mods/"); - debug_assert(modPath != 0); - wchar_t modName[PATH_MAX]; - // .. NOTE: can't use %ls, of course (keeps going beyond '/') - int matches = swscanf(modPath, L"mods/%l[^/]", modName); - debug_assert(matches == 1); - - // build full name: cache, then mod name, XMB subdir, original XMB path - xmbActualPath = VfsPath(L"cache/mods") / modName / L"xmb" / xmbFilename; + // Arbitrary version number - change this if we update the code and + // need to invalidate old users' caches + version = 1; } PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename) { debug_assert(g_XeromycesStarted); - // Make sure the .xml actually exists - if (! FileExists(vfs, filename)) - { - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Failed to find XML file %ls", filename.string().c_str()); - return PSRETURN_Xeromyces_XMLOpenFailed; - } - - // Get some data about the .xml file - FileInfo fileInfo; - if (vfs->GetFileInfo(filename, &fileInfo) < 0) - { - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Failed to stat XML file %ls", filename.string().c_str()); - return PSRETURN_Xeromyces_XMLOpenFailed; - } - - - /* - XMBs are stored with a unique name, where the name is generated from - characteristics of the XML file. If a file already exists with the - generated name, it is assumed that that file is a valid conversion of - the XML, and so it's loaded. Otherwise, the XMB is created with that - filename. - - This means it's never necessary to overwrite existing XMB files; since - the XMBs are often in archives, it's not easy to rewrite those files, - and it's not possible to switch to using a loose file because the VFS - has already decided that file is inside an archive. So each XMB is given - a unique name, and old ones are somehow purged. - */ - - - // Generate the filename for the xmb: - // _.xmb - // with mtime/size as 8-digit hex, where mtime's lowest bit is - // zeroed because zip files only have 2 second resolution. - const int suffixLength = 22; - wchar_t suffix[suffixLength+1]; - int printed = swprintf_s(suffix, ARRAY_SIZE(suffix), L"_%08x%08xB.xmb", (int)(fileInfo.MTime() & ~1), (int)fileInfo.Size()); - debug_assert(printed == suffixLength); - VfsPath xmbFilename = change_extension(filename, suffix); + CCacheLoader cacheLoader(vfs, L".xmb"); + MD5 hash; + u32 version; + PrepareCacheKey(hash, version); VfsPath xmbPath; - GetXMBPath(vfs, filename, xmbFilename, xmbPath); + LibError ret = cacheLoader.TryLoadingCached(filename, MD5(), version, xmbPath); - // If the file exists, use it - if (FileExists(vfs, xmbPath)) + if (ret == INFO::OK) { + // Found a cached XMB - load it if (ReadXMBFile(vfs, xmbPath)) return PSRETURN_OK; - // (no longer return PSRETURN_Xeromyces_XMLOpenFailed here because - // failure legitimately happens due to partially-written XMB files.) + // If this fails then we'll continue and (re)create the loose cache - + // this failure legitimately happens due to partially-written XMB files. + } + else if (ret == INFO::SKIPPED) + { + // No cached version was found - we'll need to create it + } + else + { + debug_assert(ret < 0); + + // No source file or archive cache was found, so we can't load the + // XML file at all + return PSRETURN_Xeromyces_XMLOpenFailed; } - - // XMB isn't up to date with the XML, so rebuild it: + // XMB isn't up to date with the XML, so rebuild it + return ConvertFile(vfs, filename, xmbPath); +} +bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath) +{ + CCacheLoader cacheLoader(vfs, L".xmb"); + MD5 hash; + u32 version; + PrepareCacheKey(hash, version); + + archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); + + return (ConvertFile(vfs, sourcePath, L"cache"/archiveCachePath) == PSRETURN_OK); +} + +PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath) +{ CVFSFile input; if (input.Load(vfs, filename)) { - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Failed to open XML file %ls", filename.string().c_str()); + LOGERROR(L"CXeromyces: Failed to open XML file %ls", filename.string().c_str()); return PSRETURN_Xeromyces_XMLOpenFailed; } @@ -168,7 +135,7 @@ PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename) filename8.c_str(), NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); if (! doc) { - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Failed to parse XML file %ls", filename.string().c_str()); + LOGERROR(L"CXeromyces: Failed to parse XML file %ls", filename.string().c_str()); return PSRETURN_Xeromyces_XMLParseError; } @@ -215,7 +182,7 @@ PSRETURN CXeromyces::LoadString(const char* xml) xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); if (! doc) { - LOG(CLogger::Error, LOG_CATEGORY, L"CXeromyces: Failed to parse XML string"); + LOGERROR(L"CXeromyces: Failed to parse XML string"); return PSRETURN_Xeromyces_XMLParseError; } diff --git a/source/ps/XML/Xeromyces.h b/source/ps/XML/Xeromyces.h index 0b34788ba5..ef420f3c68 100644 --- a/source/ps/XML/Xeromyces.h +++ b/source/ps/XML/Xeromyces.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ ERROR_TYPE(Xeromyces, XMLParseError); #include "lib/file/vfs/vfs.h" class WriteBuffer; - +class MD5; typedef struct _xmlDoc xmlDoc; typedef xmlDoc* xmlDocPtr; @@ -44,23 +44,33 @@ class CXeromyces : public XMBFile friend class TestXeromyces; friend class TestXeroXMB; public: - // Load from an XML file (with invisible XMB caching). + /** + * Load from an XML file (with invisible XMB caching). + */ PSRETURN Load(const PIVFS& vfs, const VfsPath& filename); - // Load from an in-memory XML string (with no caching) + /** + * Load from an in-memory XML string (with no caching). + */ PSRETURN LoadString(const char* xml); - // Call once when initialising the program, to load libxml2. - // This should be run in the main thread, before any thread - // uses libxml2. + bool GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath); + + /** + * Call once when initialising the program, to load libxml2. + * This should be run in the main thread, before any thread uses libxml2. + */ static void Startup(); - // Call once when shutting down the program, to unload libxml2. + + /** + * Call once when shutting down the program, to unload libxml2. + */ static void Terminate(); private: + void PrepareCacheKey(MD5& hash, u32& version); - // Find out write location of the XMB file corresponding to xmlFilename - static void GetXMBPath(const PIVFS& vfs, const VfsPath& xmlFilename, const VfsPath& xmbFilename, VfsPath& xmbActualPath); + PSRETURN ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath); bool ReadXMBFile(const PIVFS& vfs, const VfsPath& filename);