1
0
forked from 0ad/0ad

add support for hotloading (i.e. reacting to directory change notifications and reloading the affected files)

also clean up VFS a bit.
ogl_tex now tolerates deleting/renaming texture files that are in use
(except for a warning message which can be 'continue'd)

This was SVN commit r7166.
This commit is contained in:
janwas 2009-11-04 22:35:54 +00:00
parent b48e877f5b
commit 4c44602ea0
8 changed files with 208 additions and 122 deletions

View File

@ -76,6 +76,7 @@ LibError RealDirectory::Store(const std::wstring& name, const shared_ptr<u8>& fi
void RealDirectory::Watch()
{
if(!m_watch)
(void)dir_watch_Add(m_path, m_watch);
}

View File

@ -36,7 +36,8 @@ LIB_API shared_ptr<u8> io_Allocate(size_t size, off_t ofs = 0);
* @return INFO::CB_CONTINUE to continue; any other value will cause the
* IO splitter to abort immediately and return that.
*
* this is useful for interleaving e.g. decompression with IOs.
* this is useful for user progress notification or processing data while
* waiting for the next I/O to complete (without the complexity of threads).
**/
typedef LibError (*IoCallback)(uintptr_t cbData, const u8* block, size_t blockSize);

View File

@ -18,12 +18,15 @@
#include "precompiled.h"
#include "vfs.h"
#include "lib/posix/posix_time.h" // usleep
#include "lib/allocators/shared_ptr.h"
#include "lib/path_util.h"
#include "lib/file/common/file_stats.h"
#include "lib/file/common/trace.h"
#include "lib/file/archive/archive.h"
#include "lib/file/io/io.h"
#include "lib/res/h_mgr.h" // h_reload
#include "lib/sysdep/dir_watch.h"
#include "vfs_tree.h"
#include "vfs_lookup.h"
#include "vfs_populate.h"
@ -92,8 +95,6 @@ public:
return INFO::OK;
}
// note: only allowing either reads or writes simplifies file cache coherency
// (we need only invalidate when closing a newly written file).
virtual LibError CreateFile(const VfsPath& pathname, const shared_ptr<u8>& fileContents, size_t size)
{
VfsDirectory* directory;
@ -114,15 +115,6 @@ public:
return INFO::OK;
}
// read the entire file.
// return number of bytes transferred (see above), or a negative error code.
//
// if non-NULL, <cb> is called for each block transferred, passing <cbData>.
// it returns how much data was actually transferred, or a negative error
// code (in which case we abort the transfer and return that value).
// the callback mechanism is useful for user progress notification or
// processing data while waiting for the next I/O to complete
// (quasi-parallel, without the complexity of threads).
virtual LibError LoadFile(const VfsPath& pathname, shared_ptr<u8>& fileContents, size_t& size)
{
const bool isCacheHit = m_fileCache.Retrieve(pathname, fileContents, size);
@ -155,21 +147,15 @@ public:
return INFO::OK;
}
// rebuild the VFS, i.e. re-mount everything. open files are not affected.
// necessary after loose files or directories change, so that the VFS
// "notices" the changes and updates file locations. res calls this after
// dir_watch reports changes; can also be called from the console after a
// rebuild command. there is no provision for updating single VFS dirs -
// it's not worth the trouble.
virtual void Clear()
{
ClearR(m_rootDirectory);
m_rootDirectory.Clear();
}
virtual std::wstring TextRepresentation() const
{
std::wstring textRepresentation;
textRepresentation.reserve(100000);
textRepresentation.reserve(100*KiB);
DirectoryDescriptionR(textRepresentation, m_rootDirectory, 0);
return textRepresentation;
}
@ -183,34 +169,81 @@ public:
return INFO::OK;
}
private:
void ClearR(VfsDirectory& directory)
virtual LibError ReloadChangedFiles()
{
std::vector<fs::wpath> changedPathnames;
for(;;)
{
DirWatchNotification notification;
LibError ret = dir_watch_Poll(notification);
if(ret == ERR::AGAIN) // none available; done.
break;
RETURN_ERR(ret);
if(!CanIgnore(notification))
changedPathnames.push_back(notification.Pathname());
}
if(changedPathnames.empty())
return INFO::OK;
usleep(500*1000);
for(size_t i = 0; i < changedPathnames.size(); i++)
{
LibError ret = NotifyChangedR(m_rootDirectory, L"", changedPathnames[i]);
RETURN_ERR(ret);
if(ret == INFO::SKIPPED)
{
debug_printf(L"NotifyChangedR: no match found, ignored\n");
return ERR::VFS_FILE_NOT_FOUND; // NOWARN (happens if a new file was created)
}
}
return INFO::OK;
}
private:
// try to skip unnecessary work by ignoring uninteresting notifications.
static bool CanIgnore(const DirWatchNotification& notification)
{
// ignore directories
const fs::wpath& pathname = notification.Pathname();
if(pathname.leaf() == L".")
return true;
// ignore uninteresting file types (e.g. temp files, or the
// hundreds of XMB files that are generated from XML)
const std::wstring extension = fs::extension(pathname);
const wchar_t* extensionsToIgnore[] = { L".xmb", L".tmp" };
for(size_t i = 0; i < ARRAY_SIZE(extensionsToIgnore); i++)
{
if(!wcscasecmp(extension.c_str(), extensionsToIgnore[i]))
return true;
}
return false;
}
LibError NotifyChangedR(VfsDirectory& directory, const VfsPath& path, const fs::wpath& realPathname)
{
LibError ret = directory.NotifyChanged(realPathname);
if(ret == INFO::OK)
{
const VfsPath pathname(path/realPathname.leaf());
m_fileCache.Remove(pathname); // invalidate cached data
debug_printf(L"NotifyChangedR: reloading %ls\n", pathname.string().c_str());
RETURN_ERR(h_reload(pathname));
return INFO::OK;
}
VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories();
for(VfsDirectory::VfsSubdirectories::iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
{
const std::wstring& subdirectoryName = it->first;
VfsDirectory& subdirectory = it->second;
ClearR(subdirectory);
ret = NotifyChangedR(subdirectory, path/subdirectoryName, realPathname);
if(ret != INFO::SKIPPED)
return ret;
}
directory.Clear();
}
void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel) const
{
const std::wstring indentation(4*indentLevel, ' ');
const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories();
for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
{
const std::wstring& name = it->first;
const VfsDirectory& subdirectory = it->second;
descriptions += indentation;
descriptions += std::wstring(L"[") + name + L"]\n";
descriptions += FileDescriptions(subdirectory, indentLevel+1);
DirectoryDescriptionR(descriptions, subdirectory, indentLevel+1);
}
return INFO::SKIPPED;
}
size_t m_cacheSize;

View File

@ -17,7 +17,7 @@
/*
* Virtual File System API - allows transparent access to files in
* archives and modding via multiple mount points.
* archives, modding via multiple mount points and hotloading.
*/
#ifndef INCLUDED_VFS
@ -95,7 +95,7 @@ struct IVFS
* @return LibError.
*
* rationale: disallowing partial writes simplifies file cache coherency
* (need only be invalidated when closing a FILE_WRITE file).
* (we need only invalidate cached data when closing a newly written file).
**/
virtual LibError CreateFile(const VfsPath& pathname, const shared_ptr<u8>& fileContents, size_t size) = 0;
@ -113,14 +113,15 @@ struct IVFS
virtual LibError LoadFile(const VfsPath& pathname, shared_ptr<u8>& fileContents, size_t& size) = 0;
/**
* dump a text representation of the filesystem to debug output.
* @return a text representation of all files and directories.
**/
virtual std::wstring TextRepresentation() const = 0;
/**
* empty the contents of the filesystem.
*
* the effect is as if nothing had been mounted.
* this is typically only necessary when changing the set of
* mounted directories, e.g. when switching mods.
* NB: open files are not affected.
**/
virtual void Clear() = 0;
@ -130,6 +131,14 @@ struct IVFS
* this is useful when passing paths to external libraries.
**/
virtual LibError GetRealPath(const VfsPath& pathname, fs::wpath& path) = 0;
/**
* poll for directory change notifications and reload all affected files.
* must be called regularly (e.g. once a frame), else notifications
* may be lost.
* note: polling is much simpler than asynchronous notifications.
**/
virtual LibError ReloadChangedFiles() = 0;
};
typedef shared_ptr<IVFS> PIVFS;

View File

@ -131,6 +131,21 @@ void VfsDirectory::Clear()
}
LibError VfsDirectory::NotifyChanged(const fs::wpath& realPathname)
{
// we're not associated with the directory that changed
if(!m_realDirectory || m_realDirectory->Path() != realPathname.branch_path())
return INFO::SKIPPED;
// one of our files changed; ensure its new version supersedes the
// old by removing it and marking the directory for re-population.
const std::wstring name = realPathname.leaf();
m_files.erase(name);
m_shouldPopulate = 1;
return INFO::OK;
}
void VfsDirectory::SetAssociatedDirectory(const PRealDirectory& realDirectory)
{
if(!cpu_CAS(&m_shouldPopulate, 0, 1))
@ -145,11 +160,6 @@ bool VfsDirectory::ShouldPopulate()
}
void VfsDirectory::Invalidate()
{
m_shouldPopulate = 1;
}
//-----------------------------------------------------------------------------
@ -183,3 +193,21 @@ std::wstring FileDescriptions(const VfsDirectory& directory, size_t indentLevel)
return descriptions;
}
void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel)
{
const std::wstring indentation(4*indentLevel, ' ');
const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories();
for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
{
const std::wstring& name = it->first;
const VfsDirectory& subdirectory = it->second;
descriptions += indentation;
descriptions += std::wstring(L"[") + name + L"]\n";
descriptions += FileDescriptions(subdirectory, indentLevel+1);
DirectoryDescriptionR(descriptions, subdirectory, indentLevel+1);
}
}

View File

@ -121,6 +121,14 @@ public:
**/
void Clear();
/**
* check if this directory is affected by changes to <pathname>.
* @return INFO::OK if this directory was affected,
* INFO::CB_CONTINUE if this is a parent directory, or
* INFO::SKIPPED if this directory is unaffected.
**/
LibError NotifyChanged(const fs::wpath& path);
/**
* side effect: the next ShouldPopulate() will return true.
**/
@ -138,13 +146,6 @@ public:
**/
bool ShouldPopulate();
/**
* cause the directory to be (re)populated when next accessed
* by having ShouldPopulate return true.
* this is typically called in response to file change notifications.
**/
void Invalidate();
private:
VfsFiles m_files;
VfsSubdirectories m_subdirectories;
@ -164,4 +165,9 @@ extern std::wstring FileDescription(const VfsFile& file);
**/
extern std::wstring FileDescriptions(const VfsDirectory& directory, size_t indentLevel);
/**
* append each directory's files' description to the given string.
**/
void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel);
#endif // #ifndef INCLUDED_VFS_TREE

View File

@ -421,11 +421,12 @@ static LibError OglTex_reload(OglTex* ot, const VfsPath& pathname, Handle h)
if(!(ot->flags & OT_TEX_VALID))
{
shared_ptr<u8> file; size_t fileSize;
RETURN_ERR(g_VFS->LoadFile(pathname, file, fileSize));
if(tex_decode(file, fileSize, &ot->t) < 0)
return 0;
}
if(g_VFS->LoadFile(pathname, file, fileSize) >= 0)
{
if(tex_decode(file, fileSize, &ot->t) >= 0)
ot->flags |= OT_TEX_VALID;
}
}
glGenTextures(1, &ot->id);
@ -438,6 +439,8 @@ static LibError OglTex_reload(OglTex* ot, const VfsPath& pathname, Handle h)
}
static LibError OglTex_validate(const OglTex* ot)
{
if(ot->flags & OT_TEX_VALID)
{
RETURN_ERR(tex_validate(&ot->t));
@ -459,6 +462,7 @@ static LibError OglTex_validate(const OglTex* ot)
// TODO: ARB_texture_non_power_of_two
if(!is_pow2(w) || !is_pow2(h))
WARN_RETURN(ERR::_13);
}
// texture state
if(!filter_valid(ot->state.filter))
@ -481,7 +485,7 @@ static LibError OglTex_validate(const OglTex* ot)
static LibError OglTex_to_string(const OglTex* ot, char* buf)
{
snprintf(buf, H_STRING_LEN, "id=%d", ot->id);
snprintf(buf, H_STRING_LEN, "OglTex id=%d flags=%x", ot->id, ot->flags);
return INFO::OK;
}
@ -831,8 +835,8 @@ LibError ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint
if(ot->flags & OT_IS_UPLOADED)
return INFO::OK;
debug_assert(ot->flags & OT_TEX_VALID);
if(ot->flags & OT_TEX_VALID)
{
// decompress S3TC if that's not supported by OpenGL.
if((t->flags & TEX_DXT) && !have_s3tc)
(void)tex_transform_to(t, t->flags & ~TEX_DXT);
@ -861,7 +865,7 @@ LibError ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint
}
ogl_WarnIfError();
ot->flags |= OT_NEED_AUTO_UPLOAD|OT_IS_UPLOADED;
ot->flags |= OT_IS_UPLOADED;
// see rationale for <refs> at declaration of OglTex.
// note: tex_free is safe even if this OglTex was wrapped -
@ -873,6 +877,9 @@ LibError ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint
tex_free(t);
ot->flags &= ~OT_TEX_VALID;
}
}
ot->flags |= OT_NEED_AUTO_UPLOAD;
return INFO::OK;
}

View File

@ -41,6 +41,7 @@ that of Atlas depending on commandline parameters.
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/CmdLineArgs.h"
#include "ps/Loader.h"
#include "ps/Filesystem.h"
#include "ps/CConsole.h"
#include "ps/Profile.h"
#include "ps/Util.h"
@ -238,7 +239,7 @@ static void Frame()
{
PROFILE_START("reload changed files");
MICROLOG(L"reload changed files");
// vfs_reload_changed_files();
g_VFS->ReloadChangedFiles();
PROFILE_END( "reload changed files");
}