From 93180eb0048a92e8a31d062ca02a2d2ca0824ffd Mon Sep 17 00:00:00 2001 From: ThorstenB <brehmt@gmail.com> Date: Sun, 3 Apr 2011 15:30:25 +0200 Subject: [PATCH] On-demand loading of Nasal modules. Load a module whenever its /nasal/foo/enabled flag is set, even if it was disabled at start-up. Also expose a property if it was successfully loaded. --- src/Scripting/NasalSys.cxx | 146 ++++++++++++++++++++++++------------- src/Scripting/NasalSys.hxx | 6 +- 2 files changed, 99 insertions(+), 53 deletions(-) diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 72a99bc93..62a2b261c 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -38,6 +38,31 @@ static FGNasalSys* nasalSys = 0; +// Listener class for loading Nasal modules on demand +class FGNasalModuleListener : public SGPropertyChangeListener +{ +public: + FGNasalModuleListener(SGPropertyNode* node); + + virtual void valueChanged(SGPropertyNode* node); + +private: + SGPropertyNode_ptr _node; +}; + +FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node) +{ +} + +void FGNasalModuleListener::valueChanged(SGPropertyNode*) +{ + if (_node->getBoolValue("enabled",false)&& + !_node->getBoolValue("loaded",true)) + { + nasalSys->loadPropertyScripts(_node); + } +} + // Read and return file contents in a single buffer. Note use of // stat() to get the file size. This is a win32 function, believe it @@ -817,56 +842,73 @@ void FGNasalSys::loadPropertyScripts() SGPropertyNode* nasal = globals->get_props()->getNode("nasal"); if(!nasal) return; - for(int i=0; i<nasal->nChildren(); i++) { + for(int i=0; i<nasal->nChildren(); i++) + { SGPropertyNode* n = nasal->getChild(i); - bool is_loaded = false; - - const char* module = n->getName(); - if(n->hasChild("module")) - module = n->getStringValue("module"); - if (n->getBoolValue("enabled",true)) - { - // allow multiple files to be specified within a single - // Nasal module tag - int j = 0; - SGPropertyNode *fn; - bool file_specified = false; - while((fn = n->getChild("file", j)) != NULL) { - file_specified = true; - const char* file = fn->getStringValue(); - SGPath p(file); - if (!p.isAbsolute() || !p.exists()) - { - p = globals->resolve_maybe_aircraft_path(file); - } - loadModule(p, module); - j++; - } - - const char* src = n->getStringValue("script"); - if(!n->hasChild("script")) src = 0; // Hrm... - if(src) - createModule(module, n->getPath().c_str(), src, strlen(src)); - - if(!file_specified && !src) - { - // module no longer exists - clear the archived "enable" flag - n->setAttribute(SGPropertyNode::USERARCHIVE,false); - SGPropertyNode* node = n->getChild("enabled",0,false); - if (node) - node->setAttribute(SGPropertyNode::USERARCHIVE,false); - - SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " << - "no <file> or <script> defined in " << - "/nasal/" << module); - } - else - is_loaded = true; - } - n->setBoolValue("loaded",is_loaded); + loadPropertyScripts(n); } } +// Loads the scripts found under /nasal in the global tree +void FGNasalSys::loadPropertyScripts(SGPropertyNode* n) +{ + bool is_loaded = false; + + const char* module = n->getName(); + if(n->hasChild("module")) + module = n->getStringValue("module"); + if (n->getBoolValue("enabled",true)) + { + // allow multiple files to be specified within a single + // Nasal module tag + int j = 0; + SGPropertyNode *fn; + bool file_specified = false; + bool ok=true; + while((fn = n->getChild("file", j)) != NULL) { + file_specified = true; + const char* file = fn->getStringValue(); + SGPath p(file); + if (!p.isAbsolute() || !p.exists()) + { + p = globals->resolve_maybe_aircraft_path(file); + } + ok &= loadModule(p, module); + j++; + } + + const char* src = n->getStringValue("script"); + if(!n->hasChild("script")) src = 0; // Hrm... + if(src) + createModule(module, n->getPath().c_str(), src, strlen(src)); + + if(!file_specified && !src) + { + // module no longer exists - clear the archived "enable" flag + n->setAttribute(SGPropertyNode::USERARCHIVE,false); + SGPropertyNode* node = n->getChild("enabled",0,false); + if (node) + node->setAttribute(SGPropertyNode::USERARCHIVE,false); + + SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " << + "no <file> or <script> defined in " << + "/nasal/" << module); + } + else + is_loaded = ok; + } + else + { + SGPropertyNode* enable = n->getChild("enabled"); + if (enable) + { + FGNasalModuleListener* listener = new FGNasalModuleListener(n); + enable->addChangeListener(listener, false); + } + } + n->setBoolValue("loaded",is_loaded); +} + // Logs a runtime error, with stack trace, to the FlightGear log stream void FGNasalSys::logError(naContext context) { @@ -884,7 +926,7 @@ void FGNasalSys::logError(naContext context) // Reads a script file, executes it, and places the resulting // namespace into the global namespace under the specified module // name. -void FGNasalSys::loadModule(SGPath file, const char* module) +bool FGNasalSys::loadModule(SGPath file, const char* module) { int len = 0; char* buf = readfile(file.c_str(), &len); @@ -892,25 +934,26 @@ void FGNasalSys::loadModule(SGPath file, const char* module) SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: could not read script file " << file.c_str() << " into module " << module); - return; + return false; } - createModule(module, file.c_str(), buf, len); + bool ok = createModule(module, file.c_str(), buf, len); delete[] buf; + return ok; } // Parse and run. Save the local variables namespace, as it will // become a sub-object of globals. The optional "arg" argument can be // used to pass an associated property node to the module, which can then // be accessed via cmdarg(). (This is, for example, used by XML dialogs.) -void FGNasalSys::createModule(const char* moduleName, const char* fileName, +bool FGNasalSys::createModule(const char* moduleName, const char* fileName, const char* src, int len, const SGPropertyNode* cmdarg, int argc, naRef* args) { naRef code = parse(fileName, src, len); if(naIsNil(code)) - return; + return false; // See if we already have a module hash to use. This allows the // user to, for example, add functions to the built-in math @@ -925,6 +968,7 @@ void FGNasalSys::createModule(const char* moduleName, const char* fileName, call(code, argc, args, locals); hashset(_globals, moduleName, locals); + return true; } void FGNasalSys::deleteModule(const char* moduleName) diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index c65316ac3..ceff996da 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -25,7 +25,7 @@ public: // Loads a nasal script from an external file and inserts it as a // global module of the specified name. - void loadModule(SGPath file, const char* moduleName); + bool loadModule(SGPath file, const char* moduleName); // Simple hook to run arbitrary source code. Returns a bool to // indicate successful execution. Does *not* return any Nasal @@ -54,7 +54,7 @@ public: // Callbacks for command and timer bindings virtual bool handleCommand(const SGPropertyNode* arg); - void createModule(const char* moduleName, const char* fileName, + bool createModule(const char* moduleName, const char* fileName, const char* src, int len, const SGPropertyNode* cmdarg=0, int argc=0, naRef*args=0); @@ -66,6 +66,7 @@ public: private: friend class FGNasalScript; friend class FGNasalListener; + friend class FGNasalModuleListener; // // FGTimer subclass for handling Nasal timer callbacks. @@ -86,6 +87,7 @@ private: static int _listenerId; void loadPropertyScripts(); + void loadPropertyScripts(SGPropertyNode* n); void loadScriptDirectory(simgear::Dir nasalDir); void addModule(string moduleName, simgear::PathList scripts); void hashset(naRef hash, const char* key, naRef val);