diff options
| -rw-r--r-- | src/AppleEvents.cc | 2 | ||||
| -rw-r--r-- | src/MainWindow.h | 21 | ||||
| -rw-r--r-- | src/ModuleCache.cc | 27 | ||||
| -rw-r--r-- | src/expr.cc | 2 | ||||
| -rw-r--r-- | src/lexer.l | 2 | ||||
| -rw-r--r-- | src/mainwin.cc | 266 | ||||
| -rw-r--r-- | src/module.cc | 66 | ||||
| -rw-r--r-- | src/module.h | 23 | ||||
| -rw-r--r-- | src/parsersettings.cc | 13 | ||||
| -rw-r--r-- | testdata/modulecache-tests/README.txt | 40 | ||||
| -rwxr-xr-x | testdata/modulecache-tests/cascade.sh | 12 | ||||
| -rwxr-xr-x | testdata/modulecache-tests/cascade2.sh | 12 | 
12 files changed, 321 insertions, 165 deletions
| diff --git a/src/AppleEvents.cc b/src/AppleEvents.cc index 1fb4f99..3102792 100644 --- a/src/AppleEvents.cc +++ b/src/AppleEvents.cc @@ -15,7 +15,7 @@ OSErr eventHandler(const AppleEvent *, AppleEvent *, SRefCon )  		if (mainwin) break;  	}  	if (mainwin) { -		mainwin->actionReloadCompile(); +		mainwin->actionReloadRenderCSG();  	}  	return noErr;  } diff --git a/src/MainWindow.h b/src/MainWindow.h index bd32bdd..79e2080 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -28,6 +28,7 @@ public:  	QTimer *autoReloadTimer;  	std::string autoReloadId; +	QTimer *waitAfterReloadTimer;  	ModuleContext top_ctx;  	FileModule *root_module;      // Result of parsing @@ -76,9 +77,8 @@ private:  	void refreshDocument();  	void updateTemporalVariables();  	bool fileChangedOnDisk(); -	bool includesChanged(); -	bool compileTopLevelDocument(bool reload); -	bool compile(bool reload, bool procevents); +	void compileTopLevelDocument(); +	void compile(bool reload, bool forcedone = false);  	void compileCSG(bool procevents);  	bool maybeSave();  	bool checkEditorModified(); @@ -103,6 +103,10 @@ private slots:  	void actionReload();  	void actionShowLibraryFolder(); +	void instantiateRoot(); +	void compileDone(bool didchange); +	void compileEnded(); +  private slots:  	void pasteViewportTranslation();  	void pasteViewportRotation(); @@ -110,10 +114,13 @@ private slots:  	void preferences();  private slots: -	void actionCompile(); +	void actionRenderCSG(); +	void csgRender(); +	void csgReloadRender();  #ifdef ENABLE_CGAL  	void actionRenderCGAL();  	void actionRenderCGALDone(class CGAL_Nef_polyhedron *); +	void cgalRender();  #endif  	void actionDisplayAST();  	void actionDisplayCSGTree(); @@ -132,6 +139,7 @@ public:  	void clearCurrentOutput();  public slots: +	void actionReloadRenderCSG();  #ifdef ENABLE_OPENCSG  	void viewModeOpenCSG();  #endif @@ -164,13 +172,16 @@ public slots:  	void helpManual();  	void helpLibrary();  	void quit(); -	void actionReloadCompile();  	void checkAutoReload(); +	void waitAfterReload();  	void autoReloadSet(bool);  private:  	static void report_func(const class AbstractNode*, void *vp, int mark); +	char const * afterCompileSlot; +	bool procevents; +	  	class ProgressWidget *progresswidget;  	class CGALWorker *cgalworker;  	QMutex consolemutex; diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc index 505015e..5ebd549 100644 --- a/src/ModuleCache.cc +++ b/src/ModuleCache.cc @@ -30,34 +30,37 @@ ModuleCache *ModuleCache::inst = NULL;  */  FileModule *ModuleCache::evaluate(const std::string &filename)  { +	bool shouldCompile = true;  	FileModule *lib_mod = NULL;  	// Create cache ID  	struct stat st;  	memset(&st, 0, sizeof(struct stat)); -	stat(filename.c_str(), &st); +	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 -	if (this->entries.find(filename) != this->entries.end() &&  -			this->entries[filename].cache_id == cache_id) { -#ifdef DEBUG -// Causes too much debug output -//		PRINTB("Using cached library: %s (%s)", filename % cache_id); -#endif +	if (this->entries.find(filename) != this->entries.end()) {  		lib_mod = &(*this->entries[filename].module); - -		BOOST_FOREACH(const FileModule::IncludeContainer::value_type &include, lib_mod->includes) { -			if (lib_mod->include_modified(include.second)) { +		if (this->entries[filename].cache_id == cache_id) { +			shouldCompile = false; +			 +			if (lib_mod->includesChanged()) {  				lib_mod = NULL; -				break; +				shouldCompile = true;  			}  		}  	} +	else { +		shouldCompile = valid; +	}  	// If cache lookup failed (non-existing or old timestamp), compile module -	if (!lib_mod) { +	if (shouldCompile) {  #ifdef DEBUG  		if (this->entries.find(filename) != this->entries.end()) {  			PRINTB("Recompiling cached library: %s (%s)", filename % cache_id); diff --git a/src/expr.cc b/src/expr.cc index 46d606f..2e069f0 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -69,7 +69,7 @@ public:  		expr.recursioncount++;   	}  	~FuncRecursionGuard() { expr.recursioncount--; } -	bool recursion_detected() const { return (expr.recursioncount > 100); } +	bool recursion_detected() const { return (expr.recursioncount > 1000); }  private:  	const Expression &expr;  }; diff --git a/src/lexer.l b/src/lexer.l index 0b8048f..3dd3b6b 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -100,7 +100,7 @@ E [Ee][+-]?{D}+  %% -include[ \t\r\n>]*"<"	{ BEGIN(cond_include); filepath = filename = "";} +include[ \t\r\n>]*"<"	{ BEGIN(cond_include); filepath = filename = ""; }  <cond_include>{  [^\t\r\n>]*"/"	{ filepath = yytext; }  [^\t\r\n>/]+	{ filename = yytext; } diff --git a/src/mainwin.cc b/src/mainwin.cc index 7ca7de5..febb325 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -206,9 +206,15 @@ MainWindow::MainWindow(const QString &filename)  	autoReloadTimer = new QTimer(this);  	autoReloadTimer->setSingleShot(false); +	autoReloadTimer->setInterval(200);  	connect(autoReloadTimer, SIGNAL(timeout()), this, SLOT(checkAutoReload())); -	connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); +	waitAfterReloadTimer = new QTimer(this); +	waitAfterReloadTimer->setSingleShot(true); +	waitAfterReloadTimer->setInterval(200); +	connect(waitAfterReloadTimer, SIGNAL(timeout()), this, SLOT(waitAfterReload())); + +	connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionRenderCSG()));  	connect(this->e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps()));  	animate_panel->hide(); @@ -288,8 +294,8 @@ MainWindow::MainWindow(const QString &filename)  	// Design menu  	connect(this->designActionAutoReload, SIGNAL(toggled(bool)), this, SLOT(autoReloadSet(bool))); -	connect(this->designActionReloadAndCompile, SIGNAL(triggered()), this, SLOT(actionReloadCompile())); -	connect(this->designActionCompile, SIGNAL(triggered()), this, SLOT(actionCompile())); +	connect(this->designActionReloadAndCompile, SIGNAL(triggered()), this, SLOT(actionReloadRenderCSG())); +	connect(this->designActionCompile, SIGNAL(triggered()), this, SLOT(actionRenderCSG()));  #ifdef ENABLE_CGAL  	connect(this->designActionCompileAndRender, SIGNAL(triggered()), this, SLOT(actionRenderCGAL()));  #else @@ -505,7 +511,8 @@ MainWindow::openFile(const QString &new_filename)  	}  #endif  	setFileName(actual_filename); - +	 +	fileChangedOnDisk(); // force cached autoReloadId to update  	refreshDocument();  	updateRecentFiles();  	if (actual_filename.isEmpty()) { @@ -580,7 +587,7 @@ void MainWindow::updateTVal()  	double fps = this->e_fps->text().toDouble(&fps_ok);  	if (fps_ok) {  		if (fps <= 0) { -			actionCompile(); +			actionRenderCSG();  		} else {  			double s = this->e_fsteps->text().toDouble();  			double t = this->e_tval->text().toDouble() + 1/s; @@ -612,14 +619,96 @@ void MainWindow::refreshDocument()  }  /*! -	Parse and evaluate the design => this->root_node - -	Returns true if something was compiled, false if nothing was changed -  and the root_node was left untouched. +	compiles the design. Calls compileDone() if anything was compiled  */ -bool MainWindow::compile(bool reload, bool procevents) +void MainWindow::compile(bool reload, bool forcedone)  { -	if (!compileTopLevelDocument(reload)) return false; +	bool shouldcompiletoplevel = false; +	bool didcompile = false; + +	// Reload checks the timestamp of the toplevel file and refreshes if necessary, +	if (reload) { +		// Refresh files if it has changed on disk +		if (fileChangedOnDisk() && checkEditorModified()) { +			shouldcompiletoplevel = true; +			refreshDocument(); +		} +		// If the file hasn't changed, we might still need to compile it +		// if we haven't yet compiled the current text. +		else { +			QString current_doc = editor->toPlainText(); +			if (current_doc != last_compiled_doc)	shouldcompiletoplevel = true; +		} +	} +	else { +		shouldcompiletoplevel = true; +	} + +	if (!shouldcompiletoplevel && this->root_module && this->root_module->includesChanged()) { +		shouldcompiletoplevel = true; +	} + +	if (shouldcompiletoplevel) { +		console->clear(); +		compileTopLevelDocument(); +		didcompile = true; +	} + +	if (this->root_module) { +		if (this->root_module->handleDependencies()) { +			PRINTB("Module cache size: %d modules", ModuleCache::instance()->size()); +			didcompile = true; +		} +	} + +	// 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->hasIncludes() || +				this->root_module->usesLibraries()) { +			this->waitAfterReloadTimer->start(); +			return; +		} +	} +	compileDone(didcompile | forcedone); +} + +void MainWindow::waitAfterReload() +{ +	if (this->root_module->handleDependencies()) { +		this->waitAfterReloadTimer->start(); +		return; +	} +	else { +		compile(true, true); // In case file itself or top-level includes changed during dependency updates +	} +} + +void MainWindow::compileDone(bool didchange) +{ +	const char *callslot; +	if (didchange) { +		instantiateRoot(); +		callslot = afterCompileSlot; +	} +	else { +		callslot = "compileEnded"; +	} + +	this->procevents = false; +	QMetaObject::invokeMethod(this, callslot); +} + +void MainWindow::compileEnded() +{ +	clearCurrentOutput(); +	GuiLocker::unlock(); +	if (designActionAutoReload->isChecked()) autoReloadTimer->start(); +} + +void MainWindow::instantiateRoot() +{ +	// Go on and instantiate root_node, then call the continuation slot    // Invalidate renderers before we kill the CSG tree  	this->qglview->setRenderer(NULL); @@ -652,7 +741,7 @@ bool MainWindow::compile(bool reload, bool procevents)  	if (this->root_module) {  		// Evaluate CSG tree  		PRINT("Compiling design (CSG Tree generation)..."); -		if (procevents) QApplication::processEvents(); +		if (this->procevents) QApplication::processEvents();  		AbstractNode::resetIndexCounter();  		this->root_inst = ModuleInstantiation("group"); @@ -677,10 +766,8 @@ bool MainWindow::compile(bool reload, bool procevents)  		} else {  			PRINT("ERROR: Compilation failed!");  		} -		if (procevents) QApplication::processEvents(); +		if (this->procevents) QApplication::processEvents();  	} - -	return true;  }  /*! @@ -973,7 +1060,10 @@ void MainWindow::actionShowLibraryFolder()  void MainWindow::actionReload()  { -	if (checkEditorModified()) refreshDocument(); +	if (checkEditorModified()) { +		fileChangedOnDisk(); // force cached autoReloadId to update +		refreshDocument(); +	}  }  void MainWindow::hideEditor() @@ -1027,7 +1117,10 @@ bool MainWindow::fileChangedOnDisk()  	if (!this->fileName.isEmpty()) {  		struct stat st;  		memset(&st, 0, sizeof(struct stat)); -		stat(this->fileName.toLocal8Bit(), &st); +		bool valid = (stat(this->fileName.toLocal8Bit(), &st) == 0); +		// If file isn't there, just return and use current editor text +		if (!valid) return false; +  		std::string newid = str(boost::format("%x.%x") % st.st_mtime % st.st_size);  		if (newid != this->autoReloadId) { @@ -1038,77 +1131,43 @@ 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; -} -  /*! -	If reload is true, does a timestamp check on the document and tries to reload it. -	Otherwise, just reparses the current document and any dependencies, updates the  -	GUI accordingly and populates this->root_module. -  	Returns true if anything was compiled.  */ -bool MainWindow::compileTopLevelDocument(bool reload) +void MainWindow::compileTopLevelDocument()  { -	bool shouldcompiletoplevel = !reload; - -	if (includesChanged()) shouldcompiletoplevel = true; - -	if (reload && fileChangedOnDisk() && checkEditorModified()) { -		shouldcompiletoplevel = true; -		refreshDocument(); -	} +	updateTemporalVariables(); -	if (shouldcompiletoplevel) { -		console->clear(); - -		updateTemporalVariables(); -		 -		this->last_compiled_doc = editor->toPlainText(); -		std::string fulltext =  -			std::string(this->last_compiled_doc.toLocal8Bit().constData()) + -			"\n" + commandline_commands; -		 -		delete this->root_module; -		this->root_module = NULL; -		 -		this->root_module = parse(fulltext.c_str(), -															this->fileName.isEmpty() ?  -															"" :  -															QFileInfo(this->fileName).absolutePath().toLocal8Bit(),  -															false); - -		if (!animate_panel->isVisible()) { -			highlighter->unhighlightLastError(); -			if (!this->root_module) { -				QTextCursor cursor = editor->textCursor(); -				cursor.setPosition(parser_error_pos); -				editor->setTextCursor(cursor); -				highlighter->highlightError( parser_error_pos ); -			} +	this->last_compiled_doc = editor->toPlainText(); +	std::string fulltext =  +		std::string(this->last_compiled_doc.toLocal8Bit().constData()) + +		"\n" + commandline_commands; +	 +	delete this->root_module; +	this->root_module = NULL; +	 +	this->root_module = parse(fulltext.c_str(), +														this->fileName.isEmpty() ?  +														"" :  +														QFileInfo(this->fileName).absolutePath().toLocal8Bit(),  +														false); +	 +	if (!animate_panel->isVisible()) { +		highlighter->unhighlightLastError(); +		if (!this->root_module) { +			QTextCursor cursor = editor->textCursor(); +			cursor.setPosition(parser_error_pos); +			editor->setTextCursor(cursor); +			highlighter->highlightError( parser_error_pos );  		} - -	} - -	bool changed = shouldcompiletoplevel; -	if (this->root_module) { -		changed |= this->root_module->handleDependencies(); -		if (changed) PRINTB("Module cache size: %d modules", ModuleCache::instance()->size());  	} - -	return changed;  }  void MainWindow::checkAutoReload()  { -	if (!this->fileName.isEmpty()) actionReloadCompile(); +	if (!this->fileName.isEmpty()) { +		actionReloadRenderCSG(); +	}  }  void MainWindow::autoReloadSet(bool on) @@ -1117,7 +1176,7 @@ void MainWindow::autoReloadSet(bool on)  	settings.setValue("design/autoReload",designActionAutoReload->isChecked());  	if (on) {  		autoReloadId = ""; -		autoReloadTimer->start(200); +		autoReloadTimer->start();  	} else {  		autoReloadTimer->stop();  	} @@ -1139,15 +1198,22 @@ bool MainWindow::checkEditorModified()  	return true;  } -void MainWindow::actionReloadCompile() +void MainWindow::actionReloadRenderCSG()  {  	if (GuiLocker::isLocked()) return; -	GuiLocker lock; +	GuiLocker::lock(); +	autoReloadTimer->stop();  	setCurrentOutput();  	// PRINT("Parsing design (AST generation)...");  	// QApplication::processEvents(); -	if (!compile(true, true)) return; +	this->afterCompileSlot = "csgReloadRender"; +	this->procevents = true; +	compile(true); +} + +void MainWindow::csgReloadRender() +{  	if (this->root_node) compileCSG(true);  	// Go to non-CGAL view mode @@ -1161,20 +1227,25 @@ void MainWindow::actionReloadCompile()  		viewModeThrownTogether();  #endif  	} - -	clearCurrentOutput(); +	compileEnded();  } -void MainWindow::actionCompile() +void MainWindow::actionRenderCSG()  {  	if (GuiLocker::isLocked()) return; -	GuiLocker lock; +	GuiLocker::lock(); +	autoReloadTimer->stop();  	setCurrentOutput(); -	console->clear();  	PRINT("Parsing design (AST generation)...");  	QApplication::processEvents(); -	compile(false, !viewActionAnimate->isChecked()); +	this->afterCompileSlot = "csgRender"; +	this->procevents = !viewActionAnimate->isChecked(); +	compile(false); +} + +void MainWindow::csgRender() +{  	if (this->root_node) compileCSG(!viewActionAnimate->isChecked());  	// Go to non-CGAL view mode @@ -1198,7 +1269,7 @@ void MainWindow::actionCompile()  		img.save(filename, "PNG");  	} -	clearCurrentOutput(); +	compileEnded();  }  #ifdef ENABLE_CGAL @@ -1206,15 +1277,19 @@ void MainWindow::actionCompile()  void MainWindow::actionRenderCGAL()  {  	if (GuiLocker::isLocked()) return; -	GuiLocker lock; - +	GuiLocker::lock(); +	autoReloadTimer->stop();  	setCurrentOutput(); -	console->clear();  	PRINT("Parsing design (AST generation)...");  	QApplication::processEvents(); -	compile(false, true); +	this->afterCompileSlot = "cgalRender"; +	this->procevents = true; +	compile(false); +} +void MainWindow::cgalRender() +{  	if (!this->root_module || !this->root_node) {  		return;  	} @@ -1234,7 +1309,6 @@ void MainWindow::actionRenderCGAL()  	progress_report_prep(this->root_node, report_func, this); -	GuiLocker::lock(); // Will be unlocked in actionRenderCGALDone()  	this->cgalworker->start(this->tree);  } @@ -1297,9 +1371,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N)  	this->statusBar()->removeWidget(this->progresswidget);  	delete this->progresswidget;  	this->progresswidget = NULL; -	clearCurrentOutput(); - -	GuiLocker::unlock(); +	compileEnded();  }  #endif /* ENABLE_CGAL */ @@ -1612,7 +1684,7 @@ void MainWindow::viewModeAnimate()  {  	if (viewActionAnimate->isChecked()) {  		animate_panel->show(); -		actionCompile(); +		actionRenderCSG();  		updatedFps();  	} else {  		animate_panel->hide(); diff --git a/src/module.cc b/src/module.cc index cc0f99c..046d0c4 100644 --- a/src/module.cc +++ b/src/module.cc @@ -166,7 +166,7 @@ public:  	~ModRecursionGuard() {   		inst.recursioncount--;   	} -	bool recursion_detected() const { return (inst.recursioncount > 100); } +	bool recursion_detected() const { return (inst.recursioncount > 1000); }  private:  	const ModuleInstantiation &inst;  }; @@ -226,6 +226,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. @@ -236,35 +258,49 @@ 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<std::pair<std::string, FileModule*> > modified_modules;  	FileModule::ModuleContainer::iterator iter = this->usedlibs.begin();  	while (iter != this->usedlibs.end()) {  		FileModule::ModuleContainer::iterator curr = iter++;  		FileModule *oldmodule = curr->second; +		bool wasmissing = false;  		// Get an absolute filename for the module  		std::string filename = curr->first;  		if (!boosty::is_absolute(filename)) { +			wasmissing = true;  			fs::path fullpath = find_valid_path(this->path, filename);  			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) { -			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;  	return changed; @@ -286,17 +322,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 96e5649..682e65b 100644 --- a/src/module.h +++ b/src/module.h @@ -78,12 +78,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 @@ -95,15 +89,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<std::string, class FileModule*> 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<std::string, struct IncludeFile> IncludeContainer;  	IncludeContainer includes; -	bool include_modified(struct IncludeFile inc); -private: +  	bool is_handling_dependencies;  	std::string path;  }; diff --git a/src/parsersettings.cc b/src/parsersettings.cc index 8db33a8..04c20ed 100644 --- a/src/parsersettings.cc +++ b/src/parsersettings.cc @@ -30,23 +30,24 @@ fs::path search_libs(const fs::path &localpath)  }  // files must be 'ordinary' - they must exist and be non-directories +// FIXME: Don't print anything from this function as it's called repeatedly +// for the automatic reload function (FileModule::include_modified())  static bool check_valid(const fs::path &p, const std::vector<std::string> *openfilenames)  {  	if (p.empty()) { -		PRINTB("WARNING: File path is blank: %s",p); +		//PRINTB("WARNING: File path is blank: %s",p);  		return false;  	}  	if (!p.has_parent_path()) { -		PRINTB("WARNING: No parent path: %s",p); +		//PRINTB("WARNING: No parent path: %s",p);  		return false;  	}  	if (!fs::exists(p)) { -		PRINTB("WARNING: File not found: %s",p); -		// searched === +		//PRINTB("WARNING: File not found: %s",p);  		return false;  	}  	if (fs::is_directory(p)) { -		PRINTB("WARNING: %s invalid - points to a directory",p); +		//PRINTB("WARNING: %s invalid - points to a directory",p);  		return false;  	}  	std::string fullname = boosty::stringy(p); @@ -54,7 +55,7 @@ static bool check_valid(const fs::path &p, const std::vector<std::string> *openf  	if (openfilenames) {  		BOOST_FOREACH(const std::string &s, *openfilenames) {  			if (s == fullname) { -				PRINTB("WARNING: circular include file %s", fullname); +				//PRINTB("WARNING: circular include file %s", fullname);  				return false;  			}  		} diff --git a/testdata/modulecache-tests/README.txt b/testdata/modulecache-tests/README.txt index ce341ca..214acc5 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  ------ @@ -84,10 +77,13 @@ Test11: Missing include file appears  o rm missing.scad  o Open includemissing.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 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,15 +92,35 @@ 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 ./cascade.sh +o Open cascadetest.scad +o Turn on Automatic Reload and Compile +o Verify that the 4 objects render correctly +o rm cascadetest.scad +o Verify that no rerendering was triggered (the 4 objects are still there) +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/cascade.sh b/testdata/modulecache-tests/cascade.sh new file mode 100755 index 0000000..5dd0ef7 --- /dev/null +++ b/testdata/modulecache-tests/cascade.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +rm cascade*.scad +echo "include <cascade-A.scad> include <cascade-B.scad> use <cascade-C.scad> use <cascade-D.scad> A(); translate([11,0,0]) B(); translate([22,0,0]) C(); translate([33,0,0]) D();" > cascadetest.scad +sleep 0.05 +echo "module A() { sphere(5); }" > cascade-A.scad +sleep 0.05 +echo "module B() { cube([8,8,8], center=true); }" > cascade-B.scad +sleep 0.05 +echo "module C() { cylinder(h=8, r=5, center=true); }" > cascade-C.scad +sleep 0.05 +echo "module D() { cylinder(h=10, r1=5, r2=0, center=true); }" > cascade-D.scad diff --git a/testdata/modulecache-tests/cascade2.sh b/testdata/modulecache-tests/cascade2.sh new file mode 100755 index 0000000..50f98e0 --- /dev/null +++ b/testdata/modulecache-tests/cascade2.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +rm cascade-*.scad +echo "include <cascade-A.scad> include <cascade-B.scad> use <cascade-C.scad> use <cascade-D.scad> A(); translate([11,0,0]) B(); translate([22,0,0]) C(); translate([33,0,0]) D();" > cascadetest.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 | 
