diff --git a/src/MainWindow.h b/src/MainWindow.h index fc64137d..79e20809 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -77,7 +77,6 @@ private: void refreshDocument(); void updateTemporalVariables(); bool fileChangedOnDisk(); - bool includesChanged(); void compileTopLevelDocument(); void compile(bool reload, bool forcedone = false); void compileCSG(bool procevents); diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc index 0e443e8c..5ebd5499 100644 --- a/src/ModuleCache.cc +++ b/src/ModuleCache.cc @@ -38,6 +38,9 @@ FileModule *ModuleCache::evaluate(const std::string &filename) memset(&st, 0, sizeof(struct stat)); bool valid = (stat(filename.c_str(), &st) == 0); + // If file isn't there, just return and let the cache retain the old module + if (!valid) return NULL; + std::string cache_id = str(boost::format("%x.%x") % st.st_mtime % st.st_size); // Lookup in cache @@ -46,12 +49,9 @@ FileModule *ModuleCache::evaluate(const std::string &filename) if (this->entries[filename].cache_id == cache_id) { shouldCompile = false; - BOOST_FOREACH(const FileModule::IncludeContainer::value_type &include, lib_mod->includes) { - if (lib_mod->include_modified(include.second)) { - lib_mod = NULL; - shouldCompile = true; - break; - } + if (lib_mod->includesChanged()) { + lib_mod = NULL; + shouldCompile = true; } } } diff --git a/src/mainwin.cc b/src/mainwin.cc index 52b432c2..9652d6ca 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -636,11 +636,12 @@ void MainWindow::compile(bool reload, bool forcedone) shouldcompiletoplevel = true; } - if (!shouldcompiletoplevel && includesChanged()) { + if (!shouldcompiletoplevel && this->root_module && this->root_module->includesChanged()) { shouldcompiletoplevel = true; } if (shouldcompiletoplevel) { + console->clear(); compileTopLevelDocument(); didcompile = true; } @@ -655,8 +656,8 @@ void MainWindow::compile(bool reload, bool forcedone) // If we're auto-reloading, listen for a cascade of changes by starting a timer // if something changed _and_ there are any external dependencies if (reload && didcompile && this->root_module) { - if (this->root_module->includes.size() > 0 || - this->root_module->usedlibs.size() > 0) { + if (this->root_module->hasIncludes() || + this->root_module->usesLibraries()) { this->waitAfterReloadTimer->start(); return; } @@ -1116,23 +1117,11 @@ bool MainWindow::fileChangedOnDisk() return false; } -bool MainWindow::includesChanged() -{ - if (this->root_module) { - BOOST_FOREACH(const FileModule::IncludeContainer::value_type &item, this->root_module->includes) { - if (this->root_module->include_modified(item.second)) return true; - } - } - return false; -} - /*! Returns true if anything was compiled. */ void MainWindow::compileTopLevelDocument() { - console->clear(); - updateTemporalVariables(); this->last_compiled_doc = editor->toPlainText(); @@ -1233,7 +1222,6 @@ void MainWindow::actionRenderCSG() GuiLocker::lock(); autoReloadTimer->stop(); setCurrentOutput(); - console->clear(); PRINT("Parsing design (AST generation)..."); QApplication::processEvents(); @@ -1278,7 +1266,6 @@ void MainWindow::actionRenderCGAL() GuiLocker::lock(); autoReloadTimer->stop(); setCurrentOutput(); - console->clear(); PRINT("Parsing design (AST generation)..."); QApplication::processEvents(); diff --git a/src/module.cc b/src/module.cc index 080a3dd2..6cd8322a 100644 --- a/src/module.cc +++ b/src/module.cc @@ -207,6 +207,28 @@ void FileModule::registerInclude(const std::string &localpath, this->includes[localpath] = inc; } +bool FileModule::includesChanged() const +{ + BOOST_FOREACH(const FileModule::IncludeContainer::value_type &item, this->includes) { + if (include_modified(item.second)) return true; + } + return false; +} + +bool FileModule::include_modified(const IncludeFile &inc) const +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + + fs::path fullpath = find_valid_path(this->path, inc.filename); + bool valid = !fullpath.empty() ? (stat(boosty::stringy(fullpath).c_str(), &st) == 0) : false; + + if (valid && !inc.valid) return true; // Detect appearance of file but not removal + if (valid && st.st_mtime > inc.mtime) return true; + + return false; +} + /*! Check if any dependencies have been modified and recompile them. Returns true if anything was recompiled. @@ -217,12 +239,13 @@ bool FileModule::handleDependencies() this->is_handling_dependencies = true; bool changed = false; - // Iterating manually since we want to modify the container while iterating - // 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. + + // Iterating manually since we want to modify the container while iterating + std::vector > modified_modules; FileModule::ModuleContainer::iterator iter = this->usedlibs.begin(); while (iter != this->usedlibs.end()) { FileModule::ModuleContainer::iterator curr = iter++; @@ -237,16 +260,27 @@ bool FileModule::handleDependencies() if (!fullpath.empty()) filename = boosty::stringy(fullpath); } - curr->second = ModuleCache::instance()->evaluate(filename); - if (curr->second != oldmodule) { + FileModule *newmodule = ModuleCache::instance()->evaluate(filename); + // Detect appearance but not removal of files + if (newmodule && oldmodule != newmodule) { changed = true; #ifdef DEBUG - PRINTB_NOCACHE(" %s: %p", filename % curr->second); + PRINTB_NOCACHE(" %s: %p", filename % newmodule); #endif } - if (!curr->second && !wasmissing) { - PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", filename); + if (newmodule) { + modified_modules.push_back(std::make_pair(filename, newmodule)); + this->usedlibs.erase(curr); } + else { + // Only print warning if we're not part of an automatic reload + if (!oldmodule && !wasmissing) { + PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", filename); + } + } + } + BOOST_FOREACH(const FileModule::ModuleContainer::value_type &mod, modified_modules) { + this->usedlibs[mod.first] = mod.second; } this->is_handling_dependencies = false; @@ -269,17 +303,3 @@ AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiat return node; } - -bool FileModule::include_modified(IncludeFile inc) -{ - struct stat st; - memset(&st, 0, sizeof(struct stat)); - - fs::path fullpath = find_valid_path(this->path, inc.filename); - bool valid = !fullpath.empty() ? (stat(boosty::stringy(fullpath).c_str(), &st) == 0) : false; - - if (valid != inc.valid) return true; - if (valid && st.st_mtime > inc.mtime) return true; - - return false; -} diff --git a/src/module.h b/src/module.h index 5dfb8c46..ad2a46ce 100644 --- a/src/module.h +++ b/src/module.h @@ -77,12 +77,6 @@ public: LocalScope scope; }; -struct IncludeFile { - std::string filename; - bool valid; - time_t mtime; -}; - // FIXME: A FileModule doesn't have definition arguments, so we shouldn't really // inherit from a Module class FileModule : public Module @@ -94,15 +88,28 @@ public: void setModulePath(const std::string &path) { this->path = path; } const std::string &modulePath() const { return this->path; } void registerInclude(const std::string &localpath, const std::string &fullpath); + bool includesChanged() const; bool handleDependencies(); virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx = NULL) const; + bool hasIncludes() const { return !this->includes.empty(); } + bool usesLibraries() const { return !this->usedlibs.empty(); } + + typedef boost::unordered_map ModuleContainer; ModuleContainer usedlibs; +private: + struct IncludeFile { + std::string filename; + bool valid; + time_t mtime; + }; + + bool include_modified(const IncludeFile &inc) const; + typedef boost::unordered_map IncludeContainer; IncludeContainer includes; - bool include_modified(struct IncludeFile inc); -private: + bool is_handling_dependencies; std::string path; }; diff --git a/testdata/modulecache-tests/README.txt b/testdata/modulecache-tests/README.txt index a46e3d31..c38822f9 100644 --- a/testdata/modulecache-tests/README.txt +++ b/testdata/modulecache-tests/README.txt @@ -26,13 +26,6 @@ o Open use-mcad.scad o Compile (F5) o Check that you get a rounded box -Test4: USE Non-existing file ------- - -o Open usenonexsistingfile.scad -o Compile (F5) -o Verify that you get: WARNING: Can't open 'use' file 'nofile.scad'. - Test5: Overload USEd module ------ @@ -86,8 +79,11 @@ o Open includemissing.scad o Compile (F5) o Verify that you get: WARNING: Can't open include file 'missing.scad'. o echo "module missing() { sphere(10); }" > missing.scad +o Reload and Compile (F4) - verify that the sphere appeared o rm missing.scad -o Reload and Compile (F4) - verify that the sphere is gone +o Reload and Compile (F4) - verify that the sphere is still there +o echo "module missing() { sphere(20); }" > missing.scad +o Reload and Compile (F4) - verify that the sphere increased in size Test12: Missing include file in subpath appears ------ @@ -96,25 +92,33 @@ o Open includemissingsub.scad o Compile (F5) o Verify that you get: WARNING: Can't open include file 'subdir/missingsub.scad'. o echo "module missingsub() { sphere(10); }" > subdir/missingsub.scad +o Reload and Compile (F4) - verify that the sphere appeared o rm subdir/missingsub.scad -o Reload and Compile (F4) - verify that the sphere is gone +o Reload and Compile (F4) - verify that the sphere is still there +o echo "module missingsub() { sphere(20); }" > subdir/missingsub.scad +o Reload and Compile (F4) - verify that the sphere increased in size Test13: Missing library file appears ------- o rm missing.scad o Open usemissing.scad o Compile (F5) -o Verify that you get: WARNING: Can't open 'use' file 'missing.scad'. +o Verify that you get: WARNING: Can't open library file 'missing.scad'. o echo "module missing() { sphere(10); }" > missing.scad +o Reload and Compile (F4) - verify that the sphere appeared o rm missing.scad -o Compile (F5) - verify that the sphere is gone +o Reload and Compile (F4) - verify that the sphere is still there +o echo "module missing() { sphere(20); }" > missing.scad +o Reload and Compile (F4) - verify that the sphere increased in size Test14: Automatic reload of cascading changes ------- -o rm cascade-*.scad +o ./cascade.sh o Open cascadetest.scad o Turn on Automatic Reload and Compile o Verify that the 4 objects render correctly -o ./cascade.sh +o rm cascade-*.scad +o Verify that no rerendering was triggered (the 4 objects are still there) +o ./cascade2.sh o Verify that everything reloads at once without flickering diff --git a/testdata/modulecache-tests/cascade2.sh b/testdata/modulecache-tests/cascade2.sh new file mode 100755 index 00000000..aa17c6c8 --- /dev/null +++ b/testdata/modulecache-tests/cascade2.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +rm cascade-*.scad +sleep 0.1 +echo "module A() { sphere(6); }" > cascade-A.scad +sleep 0.1 +echo "module B() { cube([10,10,10], center=true); }" > cascade-B.scad +sleep 0.1 +echo "module C() { cylinder(h=10, r=6, center=true); }" > cascade-C.scad +sleep 0.1 +echo "module D() { cylinder(h=12, r1=6, r2=0, center=true); }" > cascade-D.scad