diff options
author | Marius Kintel <marius@kintel.net> | 2012-02-04 00:43:53 (GMT) |
---|---|---|
committer | Marius Kintel <marius@kintel.net> | 2012-02-15 00:32:37 (GMT) |
commit | b9d218e13760f21be5aa94c15631d63b29439e18 (patch) | |
tree | f01d1e16766b5af06939aa531e84b40bcf6f2f31 | |
parent | 44ff85399011ea1bd3eb812ce97d312ccc4f32eb (diff) |
Initial implementation of ModuleCache and dependency tracking of used modules
-rw-r--r-- | doc/TODO.txt | 7 | ||||
-rw-r--r-- | openscad.pro | 2 | ||||
-rw-r--r-- | src/MainWindow.h | 2 | ||||
-rw-r--r-- | src/ModuleCache.cc | 87 | ||||
-rw-r--r-- | src/ModuleCache.h | 22 | ||||
-rw-r--r-- | src/context.cc | 1 | ||||
-rw-r--r-- | src/lexer.l | 3 | ||||
-rw-r--r-- | src/mainwin.cc | 7 | ||||
-rw-r--r-- | src/module.cc | 17 | ||||
-rw-r--r-- | src/module.h | 9 | ||||
-rw-r--r-- | src/openscad.cc | 3 | ||||
-rw-r--r-- | src/openscad.h | 2 | ||||
-rw-r--r-- | src/parser.y | 122 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/modulecachetest.cc | 121 | ||||
-rw-r--r-- | tests/tests-common.cc | 7 | ||||
-rw-r--r-- | tests/tests-common.h | 2 |
17 files changed, 307 insertions, 114 deletions
diff --git a/doc/TODO.txt b/doc/TODO.txt index 60078e2..5bb66af 100644 --- a/doc/TODO.txt +++ b/doc/TODO.txt @@ -256,8 +256,13 @@ o Caching - Test that caching is actually performed (speedup + same results) - Test the modifier characters correctly influence the cache (also when added/removed) + - Test the individual caches + - PolySetCache + - CGALCache + - nodecache + - ModuleCache o other tests - export - cmd-line tests - leaf nodes having children, e.g. cube() cylinder(); - - dependency tracking + - dependency tracking (use and include) diff --git a/openscad.pro b/openscad.pro index 100785b..453ab77 100644 --- a/openscad.pro +++ b/openscad.pro @@ -188,6 +188,7 @@ HEADERS += src/parsersettings.h \ src/traverser.h \ src/nodecache.h \ src/nodedumper.h \ + src/ModuleCache.h \ src/PolySetCache.h \ src/PolySetEvaluator.h \ src/CSGTermEvaluator.h \ @@ -230,6 +231,7 @@ SOURCES += src/mathc99.cc \ src/nodedumper.cc \ src/traverser.cc \ src/PolySetEvaluator.cc \ + src/ModuleCache.cc \ src/PolySetCache.cc \ src/Tree.cc \ \ diff --git a/src/MainWindow.h b/src/MainWindow.h index 226689a..a0353a9 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -30,7 +30,7 @@ public: QString autoReloadInfo; Context root_ctx; - AbstractModule *root_module; // Result of parsing + Module *root_module; // Result of parsing ModuleInstantiation root_inst; // Top level instance AbstractNode *absolute_root_node; // Result of tree evaluation AbstractNode *root_node; // Root if the root modifier (!) is used diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc new file mode 100644 index 0000000..eaa7d33 --- /dev/null +++ b/src/ModuleCache.cc @@ -0,0 +1,87 @@ +#include "ModuleCache.h" +#include "module.h" +#include "printutils.h" +#include "boosty.h" +#include "openscad.h" + +#include <stdio.h> +#include <sstream> +#include <sys/stat.h> + +ModuleCache *ModuleCache::inst = NULL; + +Module *ModuleCache::evaluate(const std::string &filename) +{ + Module *cached = NULL; + + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + + std::stringstream idstream; + idstream << std::hex << st.st_mtime << "." << st.st_size; + std::string cache_id = idstream.str(); + + if (this->entries.find(filename) != this->entries.end() && + this->entries[filename].cache_id == cache_id) { +#ifdef DEBUG + PRINTB("Using cached library: %s (%s)", filename % cache_id); +#endif + PRINTB("%s", this->entries[filename].msg); + cached = &(*this->entries[filename].module); + } + + if (cached) { + cached->handleDependencies(); + return cached; + } + else { + if (this->entries.find(filename) != this->entries.end()) { + PRINTB("Recompiling cached library: %s (%s)", filename % cache_id); + } + else { + PRINTB("Compiling library '%s'.", filename); + } + } + + FILE *fp = fopen(filename.c_str(), "rt"); + if (!fp) { + fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str()); + return NULL; + } + std::stringstream text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text << buffer; + } + fclose(fp); + + print_messages_push(); + + cache_entry e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" }; + if (this->entries.find(filename) != this->entries.end()) + delete this->entries[filename].module; + this->entries[filename] = e; + + std::string pathname = boosty::stringy(fs::path(filename).parent_path()); + Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0)); + + if (lib_mod) { + this->entries[filename].module = lib_mod; + this->entries[filename].msg = print_messages_stack.back(); + } else { + this->entries.erase(filename); + } + + print_messages_pop(); + + return lib_mod; +} + +void ModuleCache::clear() +{ + this->entries.clear(); +} + diff --git a/src/ModuleCache.h b/src/ModuleCache.h new file mode 100644 index 0000000..57ff2e7 --- /dev/null +++ b/src/ModuleCache.h @@ -0,0 +1,22 @@ +#include <string> +#include <boost/unordered_map.hpp> + +class ModuleCache +{ +public: + static ModuleCache *instance() { if (!inst) inst = new ModuleCache; return inst; } + class Module *evaluate(const std::string &filename); + void clear(); + +private: + ModuleCache() {} + ~ModuleCache() {} + + static ModuleCache *inst; + + struct cache_entry { + class Module *module; + std::string cache_id, msg; + }; + boost::unordered_map<std::string, cache_entry> entries; +}; diff --git a/src/context.cc b/src/context.cc index b9e685c..f96a45b 100644 --- a/src/context.cc +++ b/src/context.cc @@ -162,6 +162,7 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation &inst) const } if (this->usedlibs_p) { BOOST_FOREACH(const ModuleContainer::value_type &m, *this->usedlibs_p) { + assert(m.second); if (m.second->modules.find(inst.name()) != m.second->modules.end()) { Context ctx(this->parent, m.second); return m.second->modules[inst.name()]->evaluate(&ctx, &inst); diff --git a/src/lexer.l b/src/lexer.l index 5644ded..11f2aff 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -52,7 +52,7 @@ int lexerget_lineno(void); static void yyunput(int, char*) __attribute__((unused)); #endif extern const char *parser_input_buffer; -extern const char *parser_source_path; + extern std::string parser_source_path; #define YY_INPUT(buf,result,max_size) { \ if (yyin && yyin != stdin) { \ @@ -108,6 +108,7 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); } <cond_use>{ [^\t\r\n>]+ { filename = yytext; } ">" { + PRINTB("USE: %s", filename); BEGIN(INITIAL); fs::path usepath; if (boosty::is_absolute(fs::path(filename))) { diff --git a/src/mainwin.cc b/src/mainwin.cc index 9b24eea..8eebf09 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -25,6 +25,7 @@ */ #include "PolySetCache.h" +#include "ModuleCache.h" #include "MainWindow.h" #include "openscad.h" // examplesdir #include "parsersettings.h" @@ -695,6 +696,8 @@ void MainWindow::compile(bool procevents) goto fail; } + this->root_module->handleDependencies(); + // Evaluate CSG tree PRINT("Compiling design (CSG Tree generation)..."); if (procevents) @@ -1449,7 +1452,7 @@ void MainWindow::actionFlushCaches() #endif dxf_dim_cache.clear(); dxf_cross_cache.clear(); - Module::clear_library_cache(); + ModuleCache::instance()->clear(); } void MainWindow::viewModeActionsUncheck() @@ -1797,7 +1800,7 @@ void MainWindow::consoleOutput(const std::string &msg, void *userdata) void MainWindow::setCurrentOutput() { - set_output_handler(&MainWindow::consoleOutput, this); +// set_output_handler(&MainWindow::consoleOutput, this); } void MainWindow::clearCurrentOutput() diff --git a/src/module.cc b/src/module.cc index 6641ff7..7ad2e33 100644 --- a/src/module.cc +++ b/src/module.cc @@ -25,11 +25,13 @@ */ #include "module.h" +#include "ModuleCache.h" #include "node.h" #include "context.h" #include "expression.h" #include "function.h" #include "printutils.h" + #include <boost/foreach.hpp> #include <sstream> @@ -201,7 +203,18 @@ std::string Module::dump(const std::string &indent, const std::string &name) con return dump.str(); } -void Module::clear_library_cache() +void Module::handleDependencies() { - Module::libs_cache.clear(); + PRINTB_NOCACHE("Module::handleDependencies(): %p (%d libs %p)", this % this->usedlibs.size() % &this->usedlibs); + // Iterating manually since we want to modify the container while iterating + Module::ModuleContainer::iterator iter = this->usedlibs.begin(); + while (iter != this->usedlibs.end()) { + Module::ModuleContainer::iterator curr = iter++; + curr->second = ModuleCache::instance()->evaluate(curr->first); + PRINTB_NOCACHE(" %s: %p", curr->first % curr->second); + if (!curr->second) { + PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", curr->first); + this->usedlibs.erase(curr); + } + } } diff --git a/src/module.h b/src/module.h index 6c6529b..a490129 100644 --- a/src/module.h +++ b/src/module.h @@ -65,11 +65,9 @@ public: void addChild(ModuleInstantiation *ch) { this->children.push_back(ch); } - static Module *compile_library(const std::string &filename); - static void clear_library_cache(); - typedef boost::unordered_map<std::string, class Module*> ModuleContainer; ModuleContainer usedlibs; + void handleDependencies(); std::vector<std::string> assignments_var; std::vector<Expression*> assignments_expr; @@ -87,11 +85,6 @@ public: protected: private: - struct libs_cache_ent { - Module *mod; - std::string cache_id, msg; - }; - static boost::unordered_map<std::string, libs_cache_ent> libs_cache; }; #endif diff --git a/src/openscad.cc b/src/openscad.cc index 11dad7c..39e3f4c 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -255,7 +255,7 @@ int main(int argc, char **argv) Context root_ctx; register_builtin(root_ctx); - AbstractModule *root_module; + Module *root_module; ModuleInstantiation root_inst; AbstractNode *root_node; @@ -278,6 +278,7 @@ int main(int argc, char **argv) std::string fpath = boosty::stringy(abspath.parent_path()); root_module = parse(text.str().c_str(), fpath.c_str(), false); if (!root_module) exit(1); + root_module->handleDependencies(); } fs::path fpath = boosty::absolute( fs::path(filename) ); diff --git a/src/openscad.h b/src/openscad.h index dab14cd..8b49ba2 100644 --- a/src/openscad.h +++ b/src/openscad.h @@ -27,7 +27,7 @@ #ifndef OPENSCAD_H #define OPENSCAD_H -extern class AbstractModule *parse(const char *text, const char *path, int debug); +extern class Module *parse(const char *text, const char *path, int debug); extern int get_fragments_from_r(double r, double fn, double fs, double fa); #include <string> diff --git a/src/parser.y b/src/parser.y index 15a754b..85fee48 100644 --- a/src/parser.y +++ b/src/parser.y @@ -43,7 +43,7 @@ #include <boost/foreach.hpp> #include <boost/filesystem.hpp> -using namespace boost::filesystem; +namespace fs = boost::filesystem; #include "boosty.h" int parser_error_pos = -1; @@ -56,7 +56,7 @@ int lexerlex_destroy(void); int lexerlex(void); std::vector<Module*> module_stack; -Module *module; +Module *currmodule; class ArgContainer { public: @@ -133,7 +133,7 @@ public: input: /* empty */ | - TOK_USE { module->usedlibs[$1] = NULL; } input | + TOK_USE { currmodule->usedlibs[$1] = NULL; } input | statement input ; inner_input: @@ -145,37 +145,38 @@ statement: '{' inner_input '}' | module_instantiation { if ($1) { - module->addChild($1); + currmodule->addChild($1); } else { delete $1; } } | TOK_ID '=' expr ';' { bool add_new_assignment = true; - for (size_t i = 0; i < module->assignments_var.size(); i++) { - if (module->assignments_var[i] != $1) + for (size_t i = 0; i < currmodule->assignments_var.size(); i++) { + if (currmodule->assignments_var[i] != $1) continue; - delete module->assignments_expr[i]; - module->assignments_expr[i] = $3; + delete currmodule->assignments_expr[i]; + currmodule->assignments_expr[i] = $3; add_new_assignment = false; } if (add_new_assignment) { - module->assignments_var.push_back($1); - module->assignments_expr.push_back($3); + currmodule->assignments_var.push_back($1); + currmodule->assignments_expr.push_back($3); free($1); } } | TOK_MODULE TOK_ID '(' arguments_decl optional_commas ')' { - Module *p = module; - module_stack.push_back(module); - module = new Module(); - p->modules[$2] = module; - module->argnames = $4->argnames; - module->argexpr = $4->argexpr; + Module *p = currmodule; + module_stack.push_back(currmodule); + currmodule = new Module(); + PRINTB_NOCACHE("New module: %s %p", $2 % currmodule); + p->modules[$2] = currmodule; + currmodule->argnames = $4->argnames; + currmodule->argexpr = $4->argexpr; free($2); delete $4; } statement { - module = module_stack.back(); + currmodule = module_stack.back(); module_stack.pop_back(); } | TOK_FUNCTION TOK_ID '(' arguments_decl optional_commas ')' '=' expr { @@ -183,7 +184,7 @@ statement: func->argnames = $4->argnames; func->argexpr = $4->argexpr; func->expr = $8; - module->functions[$2] = func; + currmodule->functions[$2] = func; free($2); delete $4; } ';' ; @@ -560,101 +561,34 @@ void yyerror (char const *s) { // FIXME: We leak memory on parser errors... PRINTB("Parser error in line %d: %s\n", lexerget_lineno() % s); - module = NULL; + currmodule = NULL; } extern void lexerdestroy(); extern FILE *lexerin; extern const char *parser_input_buffer; const char *parser_input_buffer; -const char *parser_source_path; +std::string parser_source_path; -AbstractModule *parse(const char *text, const char *path, int debug) +Module *parse(const char *text, const char *path, int debug) { + PRINT_NOCACHE("New parser"); lexerin = NULL; parser_error_pos = -1; parser_input_buffer = text; - parser_source_path = path; + parser_source_path = std::string(path); module_stack.clear(); - module = new Module(); + Module *rootmodule = currmodule = new Module(); + PRINTB_NOCACHE("New module: %s %p", "root" % rootmodule); parserdebug = debug; parserparse(); lexerdestroy(); lexerlex_destroy(); - if (!module) - return NULL; - - // Iterating manually since we want to modify the container while iterating - Module::ModuleContainer::iterator iter = module->usedlibs.begin(); - while (iter != module->usedlibs.end()) { - Module::ModuleContainer::iterator curr = iter++; - curr->second = Module::compile_library(curr->first); - if (!curr->second) { - PRINTB("WARNING: Failed to compile library '%s'.", curr->first); - module->usedlibs.erase(curr); - } - } + if (!rootmodule) return NULL; parser_error_pos = -1; - return module; -} - -boost::unordered_map<std::string, Module::libs_cache_ent> Module::libs_cache; - -Module *Module::compile_library(const std::string &filename) -{ - struct stat st; - memset(&st, 0, sizeof(struct stat)); - stat(filename.c_str(), &st); - - std::stringstream idstream; - idstream << std::hex << st.st_mtime << "." << st.st_size; - std::string cache_id = idstream.str(); - - if (libs_cache.find(filename) != libs_cache.end() && libs_cache[filename].cache_id == cache_id) { - PRINTB("%s", libs_cache[filename].msg); - return &(*libs_cache[filename].mod); - } - - FILE *fp = fopen(filename.c_str(), "rt"); - if (!fp) { - fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str()); - return NULL; - } - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - - print_messages_push(); - - PRINTB("Compiling library '%s'.", filename); - libs_cache_ent e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" }; - if (libs_cache.find(filename) != libs_cache.end()) - delete libs_cache[filename].mod; - libs_cache[filename] = e; - - Module *backup_mod = module; - std::string pathname = boosty::stringy( fs::path(filename).parent_path() ); - Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0)); - module = backup_mod; - - if (lib_mod) { - libs_cache[filename].mod = lib_mod; - libs_cache[filename].msg = print_messages_stack.back(); - } else { - libs_cache.erase(filename); - } - - print_messages_pop(); - - return lib_mod; + return rootmodule; } - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b81ce8c..fa75776 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -309,6 +309,7 @@ set(CORE_SOURCES ../src/expr.cc ../src/func.cc ../src/module.cc + ../src/ModuleCache.cc ../src/node.cc ../src/context.cc ../src/csgterm.cc @@ -403,6 +404,12 @@ add_executable(dumptest dumptest.cc) target_link_libraries(dumptest tests-common tests-nocgal ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES}) # +# modulecachetest +# +add_executable(modulecachetest modulecachetest.cc) +target_link_libraries(modulecachetest tests-common tests-nocgal ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES}) + +# # csgtexttest # add_executable(csgtexttest csgtexttest.cc CSGTextRenderer.cc CSGTextCache.cc) diff --git a/tests/modulecachetest.cc b/tests/modulecachetest.cc new file mode 100644 index 0000000..ae872e2 --- /dev/null +++ b/tests/modulecachetest.cc @@ -0,0 +1,121 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and + * Marius Kintel <marius@kintel.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "tests-common.h" +#include "openscad.h" +#include "parsersettings.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "export.h" +#include "builtin.h" +#include "Tree.h" + +#include <QCoreApplication> +#ifndef _MSC_VER +#include <getopt.h> +#endif +#include <assert.h> +#include <iostream> +#include <sstream> +#include <fstream> + +#include <boost/filesystem.hpp> +namespace fs = boost::filesystem; +#include "boosty.h" + +std::string commandline_commands; +std::string currentdir; +QString examplesdir; + +using std::string; + +int main(int argc, char **argv) +{ +#ifdef _MSC_VER + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + if (argc != 3) { + fprintf(stderr, "Usage: %s <file.scad> <output.txt>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + const char *outfilename = argv[2]; + + int rc = 0; + + Builtins::instance()->initialize(); + + QCoreApplication app(argc, argv); + fs::path original_path = fs::current_path(); + + currentdir = boosty::stringy( fs::current_path() ); + + parser_init(QCoreApplication::instance()->applicationDirPath().toStdString()); + set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries")); + + Context root_ctx; + register_builtin(root_ctx); + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + root_module = parsefile(filename); + if (!root_module) { + fprintf(stderr, "Error: Unable to parse input file\n"); + exit(1); + } + + fs::current_path(fs::path(filename).parent_path()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + delete root_node; + delete root_module; + + fs::current_path(original_path); + + fprintf(stderr, "Second parse\n"); + root_module = parsefile(filename); + if (!root_module) { + fprintf(stderr, "Error: Unable to parse second time\n"); + exit(1); + } + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + delete root_node; + delete root_module; + + Builtins::instance(true); + + return rc; +} diff --git a/tests/tests-common.cc b/tests/tests-common.cc index 1694a74..5b0cc3b 100644 --- a/tests/tests-common.cc +++ b/tests/tests-common.cc @@ -7,9 +7,9 @@ #include <QFileInfo> #include <sstream> -AbstractModule *parsefile(const char *filename) +Module *parsefile(const char *filename) { - AbstractModule *root_module = NULL; + Module *root_module = NULL; QFileInfo fileInfo(filename); handle_dep(filename); @@ -27,6 +27,9 @@ AbstractModule *parsefile(const char *filename) fclose(fp); text << "\n" << commandline_commands; root_module = parse(text.str().c_str(), fileInfo.absolutePath().toLocal8Bit(), false); + if (root_module) { + root_module->handleDependencies(); + } } return root_module; } diff --git a/tests/tests-common.h b/tests/tests-common.h index 92ebc78..0047562 100644 --- a/tests/tests-common.h +++ b/tests/tests-common.h @@ -1,6 +1,6 @@ #ifndef TESTS_COMMON_H_ #define TESTS_COMMON_H_ -class AbstractModule *parsefile(const char *filename); +class Module *parsefile(const char *filename); #endif |