Handle XMB caching with the same system as texture caching.
Fixes #694. This was SVN commit r8782.
This commit is contained in:
parent
6e37762e3c
commit
74114bf09a
@ -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;
|
||||
|
@ -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
149
source/ps/CacheLoader.cpp
Normal 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
74
source/ps/CacheLoader.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user