diff --git a/src/FileModule.cc b/src/FileModule.cc index 0ced4117..61a8a6a3 100644 --- a/src/FileModule.cc +++ b/src/FileModule.cc @@ -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> 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, diff --git a/src/FileModule.h b/src/FileModule.h index 0ef33972..b2d7898c 100644 --- a/src/FileModule.h +++ b/src/FileModule.h @@ -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 IncludeContainer; IncludeContainer includes; diff --git a/src/MainWindow.h b/src/MainWindow.h index cd1ee166..db2211ca 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -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); diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc index 3abe4346..eb881c61 100644 --- a/src/ModuleCache.cc +++ b/src/ModuleCache.cc @@ -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() diff --git a/src/ModuleCache.h b/src/ModuleCache.h index 49e9ac5c..77f03f3e 100644 --- a/src/ModuleCache.h +++ b/src/ModuleCache.h @@ -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 entries; }; diff --git a/src/mainwin.cc b/src/mainwin.cc index 2cf7f716..ec4c48e4 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -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; }