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);