1
0
forked from 0ad/0ad

Handle XMB caching with the same system as texture caching.

Fixes #694.

This was SVN commit r8782.
This commit is contained in:
Ykkrosh 2010-12-04 00:08:26 +00:00
parent 6e37762e3c
commit 74114bf09a
6 changed files with 360 additions and 223 deletions

View File

@ -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<VfsPath, std::set<boost::weak_ptr<CTexture> > >::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<VfsPath, std::set<boost::weak_ptr<CTexture> > > m_HotloadFiles;
typedef std::map<VfsPath, std::set<boost::weak_ptr<CTexture> > > HotloadFilesMap;
HotloadFilesMap m_HotloadFiles;
// Cache for the conversion settings files
typedef std::map<VfsPath, shared_ptr<CTextureConverter::SettingsFile> > SettingsFilesMap;

View File

@ -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());
}
}
}

149
source/ps/CacheLoader.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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)
}

74
source/ps/CacheLoader.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <stack>
#include <algorithm>
#include "maths/MD5.h"
#include "ps/CacheLoader.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "Xeromyces.h"
#include <libxml/parser.h>
#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:
// <xml filename>_<mtime><size><format version>.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;
}

View File

@ -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);