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:
parent
b48e877f5b
commit
4c44602ea0
@ -76,7 +76,8 @@ LibError RealDirectory::Store(const std::wstring& name, const shared_ptr<u8>& fi
|
||||
|
||||
void RealDirectory::Watch()
|
||||
{
|
||||
(void)dir_watch_Add(m_path, m_watch);
|
||||
if(!m_watch)
|
||||
(void)dir_watch_Add(m_path, m_watch);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
ot->flags |= OT_TEX_VALID;
|
||||
|
||||
glGenTextures(1, &ot->id);
|
||||
|
||||
@ -439,26 +440,29 @@ static LibError OglTex_reload(OglTex* ot, const VfsPath& pathname, Handle h)
|
||||
|
||||
static LibError OglTex_validate(const OglTex* ot)
|
||||
{
|
||||
RETURN_ERR(tex_validate(&ot->t));
|
||||
if(ot->flags & OT_TEX_VALID)
|
||||
{
|
||||
RETURN_ERR(tex_validate(&ot->t));
|
||||
|
||||
// width, height
|
||||
// (note: this is done here because tex.cpp doesn't impose any
|
||||
// restrictions on dimensions, while OpenGL does).
|
||||
GLsizei w = (GLsizei)ot->t.w;
|
||||
GLsizei h = (GLsizei)ot->t.h;
|
||||
// .. == 0; texture file probably not loaded successfully.
|
||||
if(w == 0 || h == 0)
|
||||
WARN_RETURN(ERR::_11);
|
||||
// .. greater than max supported tex dimension.
|
||||
// no-op if ogl_Init not yet called
|
||||
if(w > (GLsizei)ogl_max_tex_size || h > (GLsizei)ogl_max_tex_size)
|
||||
WARN_RETURN(ERR::_12);
|
||||
// .. not power-of-2.
|
||||
// note: we can't work around this because both NV_texture_rectangle
|
||||
// and subtexture require work for the client (changing tex coords).
|
||||
// TODO: ARB_texture_non_power_of_two
|
||||
if(!is_pow2(w) || !is_pow2(h))
|
||||
WARN_RETURN(ERR::_13);
|
||||
// width, height
|
||||
// (note: this is done here because tex.cpp doesn't impose any
|
||||
// restrictions on dimensions, while OpenGL does).
|
||||
GLsizei w = (GLsizei)ot->t.w;
|
||||
GLsizei h = (GLsizei)ot->t.h;
|
||||
// .. == 0; texture file probably not loaded successfully.
|
||||
if(w == 0 || h == 0)
|
||||
WARN_RETURN(ERR::_11);
|
||||
// .. greater than max supported tex dimension.
|
||||
// no-op if ogl_Init not yet called
|
||||
if(w > (GLsizei)ogl_max_tex_size || h > (GLsizei)ogl_max_tex_size)
|
||||
WARN_RETURN(ERR::_12);
|
||||
// .. not power-of-2.
|
||||
// note: we can't work around this because both NV_texture_rectangle
|
||||
// and subtexture require work for the client (changing tex coords).
|
||||
// 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,49 +835,52 @@ 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// determine fmt and int_fmt, allowing for user override.
|
||||
ot->fmt = choose_fmt(t->bpp, t->flags);
|
||||
if(fmt_ovr) ot->fmt = fmt_ovr;
|
||||
if(q_flags_ovr) ot->q_flags = q_flags_ovr;
|
||||
ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags);
|
||||
if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr;
|
||||
|
||||
// now actually send to OpenGL:
|
||||
ogl_WarnIfError();
|
||||
if(ot->flags & OT_TEX_VALID)
|
||||
{
|
||||
// (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can
|
||||
// fail in debug builds if OglTex.id isn't a valid texture name)
|
||||
RETURN_ERR(ogl_tex_bind(ht, ot->tmu));
|
||||
int levels_to_skip;
|
||||
if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0)
|
||||
// error => disable mipmapping
|
||||
ot->state.filter = GL_LINEAR;
|
||||
// (note: if first time, applies our defaults/previous overrides;
|
||||
// otherwise, replays all state changes)
|
||||
state_latch(&ot->state);
|
||||
upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip);
|
||||
}
|
||||
ogl_WarnIfError();
|
||||
// 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);
|
||||
|
||||
ot->flags |= OT_NEED_AUTO_UPLOAD|OT_IS_UPLOADED;
|
||||
// determine fmt and int_fmt, allowing for user override.
|
||||
ot->fmt = choose_fmt(t->bpp, t->flags);
|
||||
if(fmt_ovr) ot->fmt = fmt_ovr;
|
||||
if(q_flags_ovr) ot->q_flags = q_flags_ovr;
|
||||
ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags);
|
||||
if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr;
|
||||
|
||||
// see rationale for <refs> at declaration of OglTex.
|
||||
// note: tex_free is safe even if this OglTex was wrapped -
|
||||
// the Tex contains a mem handle.
|
||||
int refs = h_get_refcnt(ht);
|
||||
if(refs == 1)
|
||||
{
|
||||
// note: we verify above that OT_TEX_VALID is set
|
||||
tex_free(t);
|
||||
ot->flags &= ~OT_TEX_VALID;
|
||||
// now actually send to OpenGL:
|
||||
ogl_WarnIfError();
|
||||
{
|
||||
// (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can
|
||||
// fail in debug builds if OglTex.id isn't a valid texture name)
|
||||
RETURN_ERR(ogl_tex_bind(ht, ot->tmu));
|
||||
int levels_to_skip;
|
||||
if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0)
|
||||
// error => disable mipmapping
|
||||
ot->state.filter = GL_LINEAR;
|
||||
// (note: if first time, applies our defaults/previous overrides;
|
||||
// otherwise, replays all state changes)
|
||||
state_latch(&ot->state);
|
||||
upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip);
|
||||
}
|
||||
ogl_WarnIfError();
|
||||
|
||||
ot->flags |= OT_IS_UPLOADED;
|
||||
|
||||
// see rationale for <refs> at declaration of OglTex.
|
||||
// note: tex_free is safe even if this OglTex was wrapped -
|
||||
// the Tex contains a mem handle.
|
||||
int refs = h_get_refcnt(ht);
|
||||
if(refs == 1)
|
||||
{
|
||||
// note: we verify above that OT_TEX_VALID is set
|
||||
tex_free(t);
|
||||
ot->flags &= ~OT_TEX_VALID;
|
||||
}
|
||||
}
|
||||
|
||||
ot->flags |= OT_NEED_AUTO_UPLOAD;
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user