Fix for issue #1928 Automatic Reload and Preview doesn't work properly with multiple documents open.

includesChanged() and handleDependencies()now retrun the latest modification time instead of a bool changed flag.
It is then the resposibility of the client to decide if a change has occured.
This commit is contained in:
Chris Palmer 2017-02-03 18:37:15 +00:00
parent 757c43bdd3
commit c27dad760b
6 changed files with 47 additions and 37 deletions

View file

@ -66,45 +66,43 @@ void FileModule::registerUse(const std::string path) {
void FileModule::registerInclude(const std::string &localpath,
const std::string &fullpath)
{
struct stat st{};
bool valid = stat(fullpath.c_str(), &st) == 0;
this->includes[localpath] = {fullpath, valid, st.st_mtime};
this->includes[localpath] = {fullpath};
}
bool FileModule::includesChanged() const
time_t FileModule::includesChanged() const
{
time_t latest = 0;
for (const auto &item : this->includes) {
if (include_modified(item.second)) return true;
time_t mtime = include_modified(item.second);
if (mtime > latest) latest = mtime;
}
return false;
return latest;
}
bool FileModule::include_modified(const IncludeFile &inc) const
time_t FileModule::include_modified(const IncludeFile &inc) const
{
struct stat st{};
bool valid = (StatCache::stat(inc.filename.c_str(), &st) == 0);
if (valid && !inc.valid) return true; // Detect appearance of file but not removal
if (valid && st.st_mtime > inc.mtime) return true;
return false;
if (StatCache::stat(inc.filename.c_str(), &st) == 0)
return st.st_mtime;
return 0;
}
/*!
Check if any dependencies have been modified and recompile them.
Returns true if anything was recompiled.
*/
bool FileModule::handleDependencies()
time_t FileModule::handleDependencies()
{
if (this->is_handling_dependencies) return false;
if (this->is_handling_dependencies) return 0;
this->is_handling_dependencies = true;
bool somethingchanged = false;
std::vector<std::pair<std::string,std::string>> updates;
// If a lib in usedlibs was previously missing, we need to relocate it
// by searching the applicable paths. We can identify a previously missing module
// as it will have a relative path.
time_t latest = 0;
for (auto filename : this->usedlibs) {
bool wasmissing = false;
@ -127,7 +125,9 @@ bool FileModule::handleDependencies()
bool wascached = ModuleCache::instance()->isCached(filename);
FileModule *oldmodule = ModuleCache::instance()->lookup(filename);
FileModule *newmodule;
bool changed = ModuleCache::instance()->evaluate(filename, newmodule);
time_t mtime = ModuleCache::instance()->evaluate(filename, newmodule);
if (mtime > latest) latest = mtime;
bool changed = newmodule && newmodule != oldmodule;
// Detect appearance but not removal of files, and keep old module
// on compile errors (FIXME: Is this correct behavior?)
if (changed) {
@ -136,7 +136,6 @@ bool FileModule::handleDependencies()
else {
PRINTDB(" %s: %p", filename % oldmodule);
}
somethingchanged |= changed;
// Only print warning if we're not part of an automatic reload
if (!newmodule && !wascached && !wasmissing) {
PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", filename);
@ -151,7 +150,7 @@ bool FileModule::handleDependencies()
this->usedlibs.insert(files.second);
}
this->is_handling_dependencies = false;
return somethingchanged;
return latest;
}
AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiation *inst,

View file

@ -23,8 +23,8 @@ public:
const std::string &modulePath() const { return this->path; }
void registerUse(const std::string path);
void registerInclude(const std::string &localpath, const std::string &fullpath);
bool includesChanged() const;
bool handleDependencies();
time_t includesChanged() const;
time_t handleDependencies();
bool hasIncludes() const { return !this->includes.empty(); }
bool usesLibraries() const { return !this->usedlibs.empty(); }
bool isHandlingDependencies() const { return this->is_handling_dependencies; }
@ -35,11 +35,9 @@ public:
private:
struct IncludeFile {
std::string filename;
bool valid;
time_t mtime;
};
bool include_modified(const IncludeFile &inc) const;
time_t include_modified(const IncludeFile &inc) const;
typedef std::unordered_map<std::string, struct IncludeFile> IncludeContainer;
IncludeContainer includes;

View file

@ -274,6 +274,8 @@ private:
class CGALWorker *cgalworker;
QMutex consolemutex;
bool contentschanged; // Set if the source code has changes since the last render (F6)
time_t includes_mtime; // latest include mod time
time_t deps_mtime; // latest dependency mod time
signals:
void highlightError(int);

View file

@ -29,9 +29,9 @@ ModuleCache *ModuleCache::inst = nullptr;
Sets the module reference to the new module, or nullptr on any error (e.g. compile
error or file not found).
Returns true if anything was compiled (module or dependencies) and false otherwise.
Returns the latest mod time of the modul or its dependencies or includes.
*/
bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
time_t ModuleCache::evaluate(const std::string &filename, FileModule *&module)
{
module = nullptr;
FileModule *lib_mod = nullptr;
@ -43,14 +43,14 @@ bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
// Don't try to recursively evaluate - if the file changes
// during evaluation, that would be really bad.
if (lib_mod && lib_mod->isHandlingDependencies()) return false;
if (lib_mod && lib_mod->isHandlingDependencies()) return 0;
// Create cache ID
struct stat st{};
bool valid = (StatCache::stat(filename.c_str(), &st) == 0);
// If file isn't there, just return and let the cache retain the old module
if (!valid) return false;
if (!valid) return 0;
// If the file is present, we'll always cache some result
std::string cache_id = str(boost::format("%x.%x") % st.st_mtime % st.st_size);
@ -60,6 +60,7 @@ bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
if (!found) {
entry.module = nullptr;
entry.cache_id = cache_id;
entry.compile_time = 0;
}
bool shouldCompile = true;
@ -68,7 +69,7 @@ bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
if (entry.cache_id == cache_id) {
shouldCompile = false;
// Recompile if includes changed
if (lib_mod && lib_mod->includesChanged()) {
if (lib_mod && lib_mod->includesChanged() > entry.compile_time) {
lib_mod = nullptr;
shouldCompile = true;
}
@ -91,12 +92,13 @@ bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
}
#endif
entry.compile_time = time(NULL); // update compile time before parsing to avoid race condition.
std::stringstream textbuf;
{
std::ifstream ifs(filename.c_str());
if (!ifs.is_open()) {
PRINTB("WARNING: Can't open library file '%s'\n", filename);
return false;
return 0;
}
textbuf << ifs.rdbuf();
}
@ -120,9 +122,9 @@ bool ModuleCache::evaluate(const std::string &filename, FileModule *&module)
}
module = lib_mod;
bool depschanged = lib_mod ? lib_mod->handleDependencies() : false;
time_t deps_mtime = lib_mod ? lib_mod->handleDependencies() : 0;
return shouldCompile || depschanged;
return deps_mtime > entry.compile_time ? deps_mtime : entry.compile_time;
}
void ModuleCache::clear()

View file

@ -10,7 +10,7 @@ class ModuleCache
{
public:
static ModuleCache *instance() { if (!inst) inst = new ModuleCache; return inst; }
bool evaluate(const std::string &filename, class FileModule *&module);
time_t evaluate(const std::string &filename, class FileModule *&module);
class FileModule *lookup(const std::string &filename);
bool isCached(const std::string &filename);
size_t size() { return this->entries.size(); }
@ -25,6 +25,7 @@ private:
struct cache_entry {
class FileModule *module;
std::string cache_id;
time_t compile_time; // last time this module was compiled
};
std::unordered_map<std::string, cache_entry> entries;
};

View file

@ -177,7 +177,7 @@ bool MainWindow::undockMode = false;
bool MainWindow::reorderMode = false;
MainWindow::MainWindow(const QString &filename)
: root_inst("group"), library_info_dialog(NULL), font_list_dialog(NULL), procevents(false), tempFile(NULL), progresswidget(NULL), contentschanged(false)
: root_inst("group"), library_info_dialog(NULL), font_list_dialog(NULL), procevents(false), tempFile(NULL), progresswidget(NULL), contentschanged(false), includes_mtime(0), deps_mtime(0)
{
setupUi(this);
@ -969,8 +969,12 @@ void MainWindow::compile(bool reload, bool forcedone)
shouldcompiletoplevel = true;
}
if (!shouldcompiletoplevel && this->root_module && this->root_module->includesChanged()) {
shouldcompiletoplevel = true;
if (!shouldcompiletoplevel && this->root_module) {
time_t mtime = this->root_module->includesChanged();
if (mtime > this->includes_mtime) {
this->includes_mtime = mtime;
shouldcompiletoplevel = true;
}
}
if (shouldcompiletoplevel) {
@ -981,7 +985,9 @@ void MainWindow::compile(bool reload, bool forcedone)
}
if (this->root_module) {
if (this->root_module->handleDependencies()) {
time_t mtime = this->root_module->handleDependencies();
if (mtime > this->deps_mtime) {
this->deps_mtime = mtime;
PRINTB("Module cache size: %d modules", ModuleCache::instance()->size());
didcompile = true;
}
@ -1012,7 +1018,9 @@ void MainWindow::compile(bool reload, bool forcedone)
void MainWindow::waitAfterReload()
{
if (this->root_module->handleDependencies()) {
time_t mtime = this->root_module->handleDependencies();
if (mtime > this->deps_mtime) {
this->deps_mtime = mtime;
this->waitAfterReloadTimer->start();
return;
}