mirror of
https://github.com/AquariaOSE/Aquaria.git
synced 2024-11-25 09:44:02 +00:00
integrate directory watching for mod gfx in dev mode, and reload texture files that change
This commit is contained in:
parent
5676c41f45
commit
5a6810e063
10 changed files with 332 additions and 20 deletions
|
@ -812,7 +812,7 @@ void DSQ::init()
|
|||
|
||||
voiceOversEnabled = true;
|
||||
|
||||
this->setExtraTexturePath(NULL);
|
||||
this->setExtraTexturePath(NULL, false);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ void Mod::load(const std::string &p)
|
|||
}
|
||||
}
|
||||
|
||||
dsq->setExtraTexturePath((path + "graphics/").c_str());
|
||||
dsq->setExtraTexturePath((path + "graphics/").c_str(), dsq->isDeveloperKeys());
|
||||
|
||||
dsq->sound->audioPath2 = path + "audio/";
|
||||
dsq->sound->setVoicePath2(path + "audio/");
|
||||
|
@ -283,7 +283,7 @@ void Mod::setActive(bool a)
|
|||
mapRevealMethod = REVEAL_UNSPECIFIED;
|
||||
setLocalisationModPath("");
|
||||
name = path = "";
|
||||
dsq->setExtraTexturePath(NULL);
|
||||
dsq->setExtraTexturePath(NULL, false);
|
||||
dsq->sound->audioPath2 = "";
|
||||
dsq->sound->setVoicePath2("");
|
||||
SkeletalSprite::secondaryAnimationPath = "";
|
||||
|
|
|
@ -27,6 +27,8 @@ set(BBGE_SRCS
|
|||
DataStructures.h
|
||||
DebugFont.cpp
|
||||
DebugFont.h
|
||||
DirWatcher.cpp
|
||||
DirWatcher.h
|
||||
Emitter.cpp
|
||||
EngineEnums.h
|
||||
Event.cpp
|
||||
|
|
|
@ -53,6 +53,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|||
#endif
|
||||
#include "ttvfs_stdio.h"
|
||||
|
||||
#include "DirWatcher.h"
|
||||
|
||||
Core *core = 0;
|
||||
|
||||
static std::ofstream _logOut;
|
||||
|
@ -82,6 +84,22 @@ void CoreWindow::onEvent(const SDL_Event& ev)
|
|||
core->onEvent(ev);
|
||||
}
|
||||
|
||||
static void _TextureFileChanged(const std::string& fn, DirWatcher::Action act, void *ud)
|
||||
{
|
||||
TextureMgr::ReloadResult res = core->texmgr.reloadFile(fn, TextureMgr::OVERWRITE);
|
||||
switch(res)
|
||||
{
|
||||
case TextureMgr::RELOADED_OK:
|
||||
break; // all good
|
||||
case TextureMgr::FILE_ERROR:
|
||||
debugLog("Texture [" + fn + "] is not loaded in-game, ignoring");
|
||||
break;
|
||||
case TextureMgr::NOT_LOADED:
|
||||
debugLog("_TextureFileChanged(): There was an issue reloading " + fn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::resetCamera()
|
||||
{
|
||||
cameraPos = Vector(0,0);
|
||||
|
@ -315,6 +333,7 @@ Core::Core(const std::string &filesystem, const std::string& extraDataDir, int n
|
|||
sound = NULL;
|
||||
_extraDataDir = extraDataDir;
|
||||
sdlUserMouseEventID = SDL_RegisterEvents(1);
|
||||
_textureWatcherHandle = 0;
|
||||
|
||||
if (userDataSubFolder.empty())
|
||||
userDataSubFolder = appName;
|
||||
|
@ -1125,6 +1144,8 @@ void Core::run(float runTime)
|
|||
|
||||
while((runTime == -1 && !loopDone) || (runTime >0))
|
||||
{
|
||||
DirWatcher::Pump();
|
||||
|
||||
nowTicks = SDL_GetTicks();
|
||||
dt = (nowTicks-thenTicks)/1000.0;
|
||||
thenTicks = nowTicks;
|
||||
|
@ -1949,11 +1970,11 @@ void Core::clearRenderObjects()
|
|||
void Core::shutdown()
|
||||
{
|
||||
// pop all the states
|
||||
|
||||
|
||||
debugLog("Core::shutdown");
|
||||
shuttingDown = true;
|
||||
|
||||
DirWatcher::Shutdown();
|
||||
|
||||
debugLog("Shutdown Joystick Library...");
|
||||
shutdownJoystickLibrary();
|
||||
debugLog("OK");
|
||||
|
@ -2072,21 +2093,34 @@ void Core::reloadResources()
|
|||
|
||||
const std::string & Core::getBaseTexturePath() const
|
||||
{
|
||||
return texmgr.loadFromPaths.back();
|
||||
assert(texmgr.getNumLoadPaths());
|
||||
return texmgr.getLoadPath(texmgr.getNumLoadPaths() - 1);
|
||||
}
|
||||
|
||||
void Core::setExtraTexturePath(const char * dir)
|
||||
void Core::setExtraTexturePath(const char * dir, bool watch)
|
||||
{
|
||||
texmgr.loadFromPaths.resize(size_t(1) + !!dir);
|
||||
const char *paths[2];
|
||||
size_t w = 0;
|
||||
if(dir)
|
||||
texmgr.loadFromPaths[w++] = dir;
|
||||
texmgr.loadFromPaths[w] = "gfx/";
|
||||
paths[w++] = dir;
|
||||
paths[w++] = "gfx/";
|
||||
texmgr.setLoadPaths(&paths[0], w);
|
||||
|
||||
if(_textureWatcherHandle)
|
||||
{
|
||||
DirWatcher::RemoveWatch(_textureWatcherHandle);
|
||||
_textureWatcherHandle = 0;
|
||||
}
|
||||
|
||||
if(dir && watch)
|
||||
{
|
||||
_textureWatcherHandle = DirWatcher::AddWatch(dir, DirWatcher::RECURSIVE, _TextureFileChanged, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
const char *Core::getExtraTexturePath() const
|
||||
{
|
||||
return texmgr.loadFromPaths.size() > 1 ? texmgr.loadFromPaths[0].c_str() : NULL;
|
||||
return texmgr.getNumLoadPaths() > 1 ? texmgr.getLoadPath(0).c_str() : NULL;
|
||||
}
|
||||
|
||||
void Core::removeRenderObject(RenderObject *r, RemoveRenderObjectFlag flag)
|
||||
|
|
|
@ -318,7 +318,7 @@ public:
|
|||
|
||||
ParticleManager *particleManager;
|
||||
|
||||
void setExtraTexturePath(const char *dir); // pass NULL to disable secondary
|
||||
void setExtraTexturePath(const char *dir, bool watch); // pass NULL to disable secondary
|
||||
const char *getExtraTexturePath() const; // NULL when no secondary
|
||||
const std::string& getBaseTexturePath() const;
|
||||
|
||||
|
@ -527,6 +527,7 @@ public:
|
|||
private:
|
||||
std::vector<Joystick*> joysticks;
|
||||
unsigned sdlUserMouseEventID;
|
||||
size_t _textureWatcherHandle;
|
||||
};
|
||||
|
||||
extern Core *core;
|
||||
|
|
188
BBGE/DirWatcher.cpp
Normal file
188
BBGE/DirWatcher.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "DirWatcher.h"
|
||||
#include <string>
|
||||
#include <assert.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <SDL_mutex.h>
|
||||
#include "dmon.h"
|
||||
|
||||
namespace DirWatcher {
|
||||
|
||||
|
||||
|
||||
|
||||
struct WatchEntry
|
||||
{
|
||||
WatchEntry() : valid(false) {}
|
||||
Callback cb;
|
||||
void *ud;
|
||||
dmon_watch_id watch;
|
||||
std::string path;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct Pending
|
||||
{
|
||||
std::string fn;
|
||||
size_t idx;
|
||||
Action act;
|
||||
};
|
||||
|
||||
static std::vector<WatchEntry> s_watches;
|
||||
static std::vector<Pending> s_pending, s_processing;
|
||||
static SDL_mutex *s_mtx;
|
||||
|
||||
bool Init()
|
||||
{
|
||||
s_mtx = SDL_CreateMutex();
|
||||
dmon_init();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
if(!s_mtx)
|
||||
return;
|
||||
for(size_t i = 0; i < s_watches.size(); ++i)
|
||||
if(s_watches[i].valid)
|
||||
dmon_unwatch(s_watches[i].watch);
|
||||
s_watches.clear();
|
||||
s_pending.clear();
|
||||
dmon_deinit();
|
||||
SDL_DestroyMutex(s_mtx);
|
||||
s_mtx = NULL;
|
||||
}
|
||||
|
||||
void Pump()
|
||||
{
|
||||
if(!s_mtx)
|
||||
return;
|
||||
|
||||
SDL_LockMutex(s_mtx);
|
||||
const size_t N = s_pending.size();
|
||||
if(!N)
|
||||
{
|
||||
SDL_UnlockMutex(s_mtx);
|
||||
return;
|
||||
}
|
||||
|
||||
s_processing.swap(s_pending);
|
||||
assert(s_pending.empty());
|
||||
SDL_UnlockMutex(s_mtx);
|
||||
|
||||
std::ostringstream os;
|
||||
os << "Processing " << N << " queued file modification notifications...";
|
||||
debugLog(os.str());
|
||||
|
||||
std::string tmp;
|
||||
|
||||
for(size_t i = 0; i < N; ++i)
|
||||
{
|
||||
const WatchEntry& we = s_watches[s_processing[i].idx];
|
||||
if(!we.valid)
|
||||
continue;
|
||||
|
||||
tmp.clear();
|
||||
tmp += we.path;
|
||||
if(we.path[we.path.length() - 1] != '/')
|
||||
tmp += '/';
|
||||
tmp += s_processing[i].fn;
|
||||
|
||||
// Only look at files that exist on disk (filter out create->rename sequences)
|
||||
if(!exists(tmp))
|
||||
{
|
||||
tmp += " -- no longer exists, skipping";
|
||||
debugLog(tmp);
|
||||
continue;
|
||||
}
|
||||
|
||||
debugLog(tmp);
|
||||
|
||||
we.cb(tmp, s_processing[i].act, we.ud);
|
||||
}
|
||||
s_processing.clear();
|
||||
|
||||
debugLog("... callbacks done.");
|
||||
}
|
||||
|
||||
static void _watch_cb(dmon_watch_id watch_id, dmon_action action,
|
||||
const char* rootdir, const char* filepath,
|
||||
const char* oldfilepath, void* user)
|
||||
{
|
||||
size_t idx = (size_t)(uintptr_t)user;
|
||||
|
||||
Pending p;
|
||||
p.fn = filepath;
|
||||
p.idx = idx;
|
||||
p.act = UNKNOWN;
|
||||
switch(action)
|
||||
{
|
||||
case DMON_ACTION_CREATE:
|
||||
p.act = CREATED;
|
||||
break;
|
||||
case DMON_ACTION_MODIFY:
|
||||
case DMON_ACTION_MOVE:
|
||||
p.act = MODIFIED;
|
||||
break;
|
||||
default:
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
SDL_LockMutex(s_mtx);
|
||||
s_pending.push_back(p);
|
||||
SDL_UnlockMutex(s_mtx);
|
||||
}
|
||||
|
||||
size_t AddWatch(const char* path, Flags flags, Callback cb, void* ud)
|
||||
{
|
||||
const size_t N = s_watches.size();
|
||||
if(!N && !s_mtx)
|
||||
if(!Init())
|
||||
return 0;
|
||||
|
||||
size_t idx = N;
|
||||
for(size_t i = 0; i < N; ++i)
|
||||
if(!s_watches[i].valid)
|
||||
{
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
WatchEntry we;
|
||||
we.cb = cb;
|
||||
we.valid = false;
|
||||
we.path = path;
|
||||
if(idx == N)
|
||||
s_watches.push_back(we);
|
||||
else
|
||||
s_watches[idx] = we;
|
||||
|
||||
unsigned flg = DMON_WATCHFLAGS_FOLLOW_SYMLINKS;
|
||||
if(flags & RECURSIVE)
|
||||
flg = DMON_WATCHFLAGS_RECURSIVE;
|
||||
|
||||
s_watches[idx].watch = dmon_watch(path, _watch_cb, (dmon_watch_flags_t)flg, (void*)(uintptr_t)idx);
|
||||
if(!s_watches[idx].watch.id)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Failed to attach dir watcher to [" << path << "]";
|
||||
errorLog(os.str());
|
||||
return 0;
|
||||
}
|
||||
s_watches[idx].valid = true;
|
||||
return idx + 1;
|
||||
}
|
||||
|
||||
void RemoveWatch(size_t idx)
|
||||
{
|
||||
if(!idx)
|
||||
return;
|
||||
--idx;
|
||||
|
||||
assert(s_watches[idx].valid);
|
||||
dmon_unwatch(s_watches[idx].watch);
|
||||
s_watches[idx].valid = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace DirWatcher
|
31
BBGE/DirWatcher.h
Normal file
31
BBGE/DirWatcher.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "Base.h"
|
||||
|
||||
namespace DirWatcher {
|
||||
|
||||
enum Flags
|
||||
{
|
||||
NONE = 0,
|
||||
RECURSIVE = 0x01
|
||||
};
|
||||
|
||||
enum Action
|
||||
{
|
||||
UNKNOWN,
|
||||
CREATED,
|
||||
MODIFIED,
|
||||
};
|
||||
|
||||
typedef void (*Callback)(const std::string& fn, Action act, void *ud);
|
||||
|
||||
|
||||
bool Init();
|
||||
void Shutdown();
|
||||
void Pump(); // call from mainloop
|
||||
|
||||
size_t AddWatch(const char *path, Flags flags, Callback cb, void *ud); // returns > 0 on success
|
||||
void RemoveWatch(size_t idx);
|
||||
|
||||
|
||||
} // end namespace DirWatcher
|
|
@ -75,6 +75,17 @@ static const ExtAndLoader s_extAndLoader[] =
|
|||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const char *getExtension(const std::string& name)
|
||||
{
|
||||
size_t lastdot = name.rfind('.');
|
||||
size_t lastslash = name.rfind('/');
|
||||
if(lastdot != std::string::npos && (lastslash == std::string::npos || lastslash < lastdot))
|
||||
{
|
||||
return name.c_str() + lastdot + 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static TexLoader getFullnameAndLoader(const std::string& name, const std::string& basedir)
|
||||
{
|
||||
// TODO: use localisePath()
|
||||
|
@ -83,17 +94,16 @@ static TexLoader getFullnameAndLoader(const std::string& name, const std::string
|
|||
if(exists(name))
|
||||
{
|
||||
// check first if name exists and has a known extension, if so, use correct loader
|
||||
size_t lastdot = name.rfind('.');
|
||||
size_t lastslash = name.rfind('/');
|
||||
if(lastdot != std::string::npos && (lastslash == std::string::npos || lastslash < lastdot))
|
||||
const char *ext = getExtension(name);
|
||||
if(ext)
|
||||
{
|
||||
std::string ext = name.substr(lastdot + 1);
|
||||
const size_t extlen = strlen(ext);
|
||||
for(const ExtAndLoader *exl = &s_extAndLoader[0]; exl->func; ++exl)
|
||||
{
|
||||
if(ext == exl->ext)
|
||||
if(!strcmp(exl->ext, ext))
|
||||
{
|
||||
// remove basedir and extension
|
||||
std::string texname = name.substr(basedir.length(), name.length() - (basedir.length() + ext.length() + 1)); // strip extension
|
||||
std::string texname = name.substr(basedir.length(), name.length() - (basedir.length() + extlen + 1)); // strip extension
|
||||
return TexLoader(exl->func, fixup(name), texname );
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +173,24 @@ TextureMgr::~TextureMgr()
|
|||
SDL_DestroySemaphore((SDL_sem*)sem);
|
||||
}
|
||||
|
||||
void TextureMgr::setLoadPaths(const char* const* paths, size_t n)
|
||||
{
|
||||
loadFromPaths.resize(n);
|
||||
for(size_t i = 0; i < n; ++i)
|
||||
loadFromPaths[i] = paths[i];
|
||||
}
|
||||
|
||||
const std::string& TextureMgr::getLoadPath(size_t idx) const
|
||||
{
|
||||
assert(idx < loadFromPaths.size());
|
||||
return loadFromPaths[idx];
|
||||
}
|
||||
|
||||
const size_t TextureMgr::getNumLoadPaths() const
|
||||
{
|
||||
return loadFromPaths.size();
|
||||
}
|
||||
|
||||
size_t TextureMgr::spawnThreads(size_t n)
|
||||
{
|
||||
for(size_t i = 0; i < n; ++i)
|
||||
|
@ -370,4 +398,17 @@ void TextureMgr::reloadAll(LoadMode mode)
|
|||
loadBatch(NULL, &todo[0],todo.size(), mode);
|
||||
}
|
||||
|
||||
|
||||
TextureMgr::ReloadResult TextureMgr::reloadFile(const std::string& filename, LoadMode mode)
|
||||
{
|
||||
for(TexCache::iterator it = cache.begin(); it != cache.end(); ++it)
|
||||
{
|
||||
if(it->second->filename == filename)
|
||||
{
|
||||
if(load(it->second->name, mode))
|
||||
return RELOADED_OK;
|
||||
else
|
||||
return FILE_ERROR;
|
||||
}
|
||||
}
|
||||
return NOT_LOADED;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@ public:
|
|||
|
||||
typedef void (*ProgressCallback)(size_t done, void*);
|
||||
|
||||
std::vector<std::string> loadFromPaths;
|
||||
void setLoadPaths(const char * const * paths, size_t n);
|
||||
const std::string& getLoadPath(size_t idx) const;
|
||||
const size_t getNumLoadPaths() const;
|
||||
size_t spawnThreads(size_t n);
|
||||
size_t getNumLoaded() const;
|
||||
Texture *getOrLoad(const std::string& name);
|
||||
|
@ -34,6 +36,16 @@ public:
|
|||
Texture *load(const std::string& texname, LoadMode mode);
|
||||
void reloadAll(LoadMode mode);
|
||||
|
||||
enum ReloadResult
|
||||
{
|
||||
FILE_ERROR,
|
||||
RELOADED_OK,
|
||||
NOT_LOADED,
|
||||
};
|
||||
|
||||
// reload a file from disk. Pass in FILE NAME. The function figures out the texture name.
|
||||
ReloadResult reloadFile(const std::string& fn, LoadMode mode);
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, CountedPtr<Texture> > TexCache;
|
||||
TexCache cache;
|
||||
|
@ -48,6 +60,8 @@ private:
|
|||
|
||||
void th_loadFromFile(TexLoadTmp& tt) const;
|
||||
Texture *finalize(TexLoadTmp& tt);
|
||||
|
||||
std::vector<std::string> loadFromPaths;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -39,4 +39,5 @@ static unsigned char * miniz_stbi_compress(unsigned char *data, int data_len, in
|
|||
#include "qoi.h"
|
||||
|
||||
#define DMON_IMPL
|
||||
#define DMON_SLEEP_INTERVAL 250
|
||||
#include "dmon.h"
|
||||
|
|
Loading…
Reference in a new issue