1
0
Fork 0
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:
fgenesis 2024-06-25 04:49:44 +02:00
parent 5676c41f45
commit 5a6810e063
10 changed files with 332 additions and 20 deletions

View file

@ -812,7 +812,7 @@ void DSQ::init()
voiceOversEnabled = true;
this->setExtraTexturePath(NULL);
this->setExtraTexturePath(NULL, false);

View file

@ -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 = "";

View file

@ -27,6 +27,8 @@ set(BBGE_SRCS
DataStructures.h
DebugFont.cpp
DebugFont.h
DirWatcher.cpp
DirWatcher.h
Emitter.cpp
EngineEnums.h
Event.cpp

View file

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

View file

@ -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
View 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
View 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

View file

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

View file

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

View file

@ -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"