]> git.mxchange.org Git - flightgear.git/blobdiff - src/Scripting/NasalSys.cxx
Start refactoring how FGPositioned classes are exposed to Nasal, more to come.
[flightgear.git] / src / Scripting / NasalSys.cxx
index 17fdb67d5cf3112b1474ca02d126dbd90ffd0676..5f67b2c13c63fa63eb3920c85a7134ee144ca66f 100644 (file)
 #include <fstream>
 #include <sstream>
 
-#include <plib/ul.h>
-
 #include <simgear/nasal/nasal.h>
 #include <simgear/props/props.hxx>
 #include <simgear/math/sg_random.h>
 #include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/sg_dir.hxx>
 #include <simgear/misc/interpolator.hxx>
-#include <simgear/scene/material/mat.hxx>
 #include <simgear/structure/commands.hxx>
 #include <simgear/math/sg_geodesy.hxx>
 #include <simgear/structure/event_mgr.hxx>
 
-#include <Airports/runways.hxx>
-#include <Airports/simple.hxx>
+#include "NasalSys.hxx"
+#include "NasalPositioned.hxx"
 #include <Main/globals.hxx>
-#include <Main/fg_props.hxx>
 #include <Main/util.hxx>
-#include <Scenery/scenery.hxx>
-#include <Navaids/navrecord.hxx>
-#include <Navaids/procedure.hxx>
+#include <Main/fg_props.hxx>
 
-#include "NasalSys.hxx"
+using std::map;
 
 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
@@ -187,8 +207,16 @@ static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
     case props::BOOL:   case props::INT:
     case props::LONG:   case props::FLOAT:
     case props::DOUBLE:
-        return naNum(p->getDoubleValue());
-
+        {
+        double dv = p->getDoubleValue();
+        if (osg::isNaN(dv)) {
+          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
+          return naNil();
+        }
+        
+        return naNum(dv);
+        }
+        
     case props::STRING:
     case props::UNSPECIFIED:
         {
@@ -235,6 +263,11 @@ static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
             naRef n = naNumValue(val);
             if(naIsNil(n))
                 naRuntimeError(c, "setprop() value is not string or number");
+                
+            if (osg::isNaN(n.num)) {
+                naRuntimeError(c, "setprop() passed a NaN");
+            }
+            
             result = props->setDoubleValue(buf, n.num);
         }
     } catch (const string& err) {
@@ -256,7 +289,7 @@ static naRef f_print(naContext c, naRef me, int argc, naRef* args)
         if(naIsNil(s)) continue;
         buf += naStr_data(s);
     }
-    SG_LOG(SG_GENERAL, SG_ALERT, buf);
+    SG_LOG(SG_NASAL, SG_ALERT, buf);
     return naNum(buf.length());
 }
 
@@ -364,15 +397,17 @@ static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
 {
     if(argc != 1 || !naIsString(args[0]))
         naRuntimeError(c, "bad arguments to directory()");
-    naRef ldir = args[0];
-    ulDir* dir = ulOpenDir(naStr_data(args[0]));
-    if(!dir) return naNil();
+    
+    simgear::Dir d(SGPath(naStr_data(args[0])));
+    if(!d.exists()) return naNil();
     naRef result = naNewVector(c);
-    ulDirEnt* dent;
-    while((dent = ulReadDir(dir)))
-        naVec_append(result, naStr_fromdata(naNewString(c), dent->d_name,
-                                            strlen(dent->d_name)));
-    ulCloseDir(dir);
+
+    simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
+    for (unsigned int i=0; i<paths.size(); ++i) {
+      std::string p = paths[i].file();
+      naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
+    }
+    
     return result;
 }
 
@@ -437,226 +472,12 @@ static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
     // Converts from 100ns units in 1601 epoch to unix epoch in sec
     return naNum((t * 1e-7) - 11644473600.0);
 #else
-    time_t t;
     struct timeval td;
-    do { t = time(0); gettimeofday(&td, 0); } while(t != time(0));
-    return naNum(t + 1e-6 * td.tv_usec);
+    gettimeofday(&td, 0);
+    return naNum(td.tv_sec + 1e-6 * td.tv_usec);
 #endif
 }
 
-// Convert a cartesian point to a geodetic lat/lon/altitude.
-static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
-{
-    double lat, lon, alt, xyz[3];
-    if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
-    for(int i=0; i<3; i++)
-        xyz[i] = naNumValue(args[i]).num;
-    sgCartToGeod(xyz, &lat, &lon, &alt);
-    lat *= SG_RADIANS_TO_DEGREES;
-    lon *= SG_RADIANS_TO_DEGREES;
-    naRef vec = naNewVector(c);
-    naVec_append(vec, naNum(lat));
-    naVec_append(vec, naNum(lon));
-    naVec_append(vec, naNum(alt));
-    return vec;
-}
-
-// Convert a geodetic lat/lon/altitude to a cartesian point.
-static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
-{
-    if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
-    double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
-    double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
-    double alt = naNumValue(args[2]).num;
-    double xyz[3];
-    sgGeodToCart(lat, lon, alt, xyz);
-    naRef vec = naNewVector(c);
-    naVec_append(vec, naNum(xyz[0]));
-    naVec_append(vec, naNum(xyz[1]));
-    naVec_append(vec, naNum(xyz[2]));
-    return vec;
-}
-
-// For given geodetic point return array with elevation, and a material data
-// hash, or nil if there's no information available (tile not loaded). If
-// information about the material isn't available, then nil is returned instead
-// of the hash.
-static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
-{
-#define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
-    if(argc < 2 || argc > 3)
-        naRuntimeError(c, "geodinfo() expects 2 or 3 arguments: lat, lon [, maxalt]");
-    double lat = naNumValue(args[0]).num;
-    double lon = naNumValue(args[1]).num;
-    double elev = argc == 3 ? naNumValue(args[2]).num : 10000;
-    const SGMaterial *mat;
-    SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
-    if(!globals->get_scenery()->get_elevation_m(geod, elev, &mat))
-        return naNil();
-    naRef vec = naNewVector(c);
-    naVec_append(vec, naNum(elev));
-    naRef matdata = naNil();
-    if(mat) {
-        matdata = naNewHash(c);
-        naRef names = naNewVector(c);
-        const vector<string> n = mat->get_names();
-        for(unsigned int i=0; i<n.size(); i++)
-            naVec_append(names, naStr_fromdata(naNewString(c),
-                    const_cast<char*>(n[i].c_str()), n[i].size()));
-        HASHSET("names", 5, names);
-        HASHSET("solid", 5, naNum(mat->get_solid()));
-        HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
-        HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
-        HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
-        HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
-        HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
-    }
-    naVec_append(vec, matdata);
-    return vec;
-#undef HASHSET
-}
-
-
-class AirportInfoFilter : public FGAirport::AirportFilter
-{
-public:
-    AirportInfoFilter() : type(FGPositioned::AIRPORT) {
-    }
-
-    virtual FGPositioned::Type minType() const {
-        return type;
-    }
-
-    virtual FGPositioned::Type maxType() const {
-        return type;
-    }
-
-    FGPositioned::Type type;
-};
-
-// Returns data hash for particular or nearest airport of a <type>, or nil
-// on error.
-//
-// airportinfo(<id>);                   e.g. "KSFO"
-// airportinfo(<type>);                 type := ("airport"|"seaport"|"heliport")
-// airportinfo()                        same as  airportinfo("airport")
-// airportinfo(<lat>, <lon> [, <type>]);
-static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
-{
-    static SGConstPropertyNode_ptr latn = fgGetNode("/position/latitude-deg", true);
-    static SGConstPropertyNode_ptr lonn = fgGetNode("/position/longitude-deg", true);
-    SGGeod pos;
-    FGAirport* apt = NULL;
-
-    if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
-        pos = SGGeod::fromDeg(args[1].num, args[0].num);
-        args += 2;
-        argc -= 2;
-    } else {
-        pos = SGGeod::fromDeg(lonn->getDoubleValue(), latn->getDoubleValue());
-    }
-
-    double maxRange = 10000.0; // expose this? or pick a smaller value?
-
-    AirportInfoFilter filter; // defaults to airports only
-
-    if(argc == 0) {
-        // fall through and use AIRPORT
-    } else if(argc == 1 && naIsString(args[0])) {
-        const char *s = naStr_data(args[0]);
-        if(!strcmp(s, "airport")) filter.type = FGPositioned::AIRPORT;
-        else if(!strcmp(s, "seaport")) filter.type = FGPositioned::SEAPORT;
-        else if(!strcmp(s, "heliport")) filter.type = FGPositioned::HELIPORT;
-        else {
-            // user provided an <id>, hopefully
-            apt = FGAirport::findByIdent(s);
-            if (!apt) {
-                // return nil here, but don't raise a runtime error; this is a
-                // legitamate way to validate an ICAO code, for example in a
-                // dialog box or similar.
-                return naNil();
-            }
-        }
-    } else {
-        naRuntimeError(c, "airportinfo() with invalid function arguments");
-        return naNil();
-    }
-
-    if(!apt) {
-        apt = FGAirport::findClosest(pos, maxRange, &filter);
-        if(!apt) return naNil();
-    }
-
-    string id = apt->ident();
-    string name = apt->name();
-
-    // set runway hash
-    naRef rwys = naNewHash(c);
-    for(unsigned int r=0; r<apt->numRunways(); ++r) {
-        FGRunway* rwy(apt->getRunwayByIndex(r));
-
-        naRef rwyid = naStr_fromdata(naNewString(c),
-                      const_cast<char *>(rwy->ident().c_str()),
-                      rwy->ident().length());
-
-        naRef rwydata = naNewHash(c);
-#define HASHSET(s,l,n) naHash_set(rwydata, naStr_fromdata(naNewString(c),s,l),n)
-        HASHSET("id", 2, rwyid);
-        HASHSET("lat", 3, naNum(rwy->latitude()));
-        HASHSET("lon", 3, naNum(rwy->longitude()));
-        HASHSET("heading", 7, naNum(rwy->headingDeg()));
-        HASHSET("length", 6, naNum(rwy->lengthM()));
-        HASHSET("width", 5, naNum(rwy->widthM()));
-        HASHSET("threshold", 9, naNum(rwy->displacedThresholdM()));
-        HASHSET("stopway", 7, naNum(rwy->stopwayM()));
-        
-        if (rwy->ILS()) {
-          HASHSET("ils_frequency_mhz", 17, naNum(rwy->ILS()->get_freq() / 100.0));
-        }
-        
-        std::vector<flightgear::SID*> sids(rwy->getSIDs());
-        naRef sidVec = naNewVector(c);
-        
-        for (unsigned int s=0; s < sids.size(); ++s) {
-          naRef procId = naStr_fromdata(naNewString(c),
-                    const_cast<char *>(sids[s]->ident().c_str()),
-                    sids[s]->ident().length());
-          naVec_append(sidVec, procId);
-        }
-        HASHSET("sids", 4, sidVec); 
-        
-        std::vector<flightgear::STAR*> stars(rwy->getSTARs());
-        naRef starVec = naNewVector(c);
-      
-        for (unsigned int s=0; s < stars.size(); ++s) {
-          naRef procId = naStr_fromdata(naNewString(c),
-                    const_cast<char *>(stars[s]->ident().c_str()),
-                    stars[s]->ident().length());
-          naVec_append(starVec, procId);
-        }
-        HASHSET("stars", 5, starVec); 
-
-#undef HASHSET
-        naHash_set(rwys, rwyid, rwydata);
-    }
-
-    // set airport hash
-    naRef aptdata = naNewHash(c);
-#define HASHSET(s,l,n) naHash_set(aptdata, naStr_fromdata(naNewString(c),s,l),n)
-    HASHSET("id", 2, naStr_fromdata(naNewString(c),
-            const_cast<char *>(id.c_str()), id.length()));
-    HASHSET("name", 4, naStr_fromdata(naNewString(c),
-            const_cast<char *>(name.c_str()), name.length()));
-    HASHSET("lat", 3, naNum(apt->getLatitude()));
-    HASHSET("lon", 3, naNum(apt->getLongitude()));
-    HASHSET("elevation", 9, naNum(apt->getElevation() * SG_FEET_TO_METER));
-    HASHSET("has_metar", 9, naNum(apt->getMetar()));
-    HASHSET("runways", 7, rwys);
-#undef HASHSET
-    return aptdata;
-}
-
-
 // Table of extension functions.  Terminate with zeros.
 static struct { const char* name; naCFunction func; } funcs[] = {
     { "getprop",   f_getprop },
@@ -675,10 +496,6 @@ static struct { const char* name; naCFunction func; } funcs[] = {
     { "resolvepath", f_resolveDataPath },
     { "parsexml", f_parsexml },
     { "systime", f_systime },
-    { "carttogeod", f_carttogeod },
-    { "geodtocart", f_geodtocart },
-    { "geodinfo", f_geodinfo },
-    { "airportinfo", f_airportinfo },
     { 0, 0 }
 };
 
@@ -712,6 +529,8 @@ void FGNasalSys::init()
         hashset(_globals, funcs[i].name,
                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
 
+    initNasalPositioned(_globals, _context);
+  
     // And our SGPropertyNode wrapper
     hashset(_globals, "props", genPropsModule());
 
@@ -722,18 +541,17 @@ void FGNasalSys::init()
     hashset(_globals, "__gcsave", _gcHash);
 
     // Now load the various source files in the Nasal directory
-    SGPath p(globals->get_fg_root());
-    p.append("Nasal");
-    ulDirEnt* dent;
-    ulDir* dir = ulOpenDir(p.c_str());
-    while(dir && (dent = ulReadDir(dir)) != 0) {
-        SGPath fullpath(p);
-        fullpath.append(dent->d_name);
-        SGPath file(dent->d_name);
-        if(file.extension() != "nas") continue;
-        loadModule(fullpath, file.base().c_str());
+    simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
+    loadScriptDirectory(nasalDir);
+
+    // Add modules in Nasal subdirectories to property tree
+    simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
+            simgear::Dir::NO_DOT_OR_DOTDOT, "");
+    for (unsigned int i=0; i<directories.size(); ++i) {
+        simgear::Dir dir(directories[i]);
+        simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
+        addModule(directories[i].file(), scripts);
     }
-    ulCloseDir(dir);
 
     // set signal and remove node to avoid restoring at reinit
     const char *s = "nasal-dir-initialized";
@@ -753,6 +571,19 @@ void FGNasalSys::update(double)
         _dead_listener.clear();
     }
 
+    if (!_loadList.empty())
+    {
+        // process Nasal load hook (only one per update loop to avoid excessive lags)
+        _loadList.pop()->load();
+    }
+    else
+    if (!_unloadList.empty())
+    {
+        // process pending Nasal unload hooks after _all_ load hooks were processed
+        // (only unload one per update loop to avoid excessive lags)
+        _unloadList.pop()->unload();
+    }
+
     // The global context is a legacy thing.  We use dynamically
     // created contexts for naCall() now, so that we can call them
     // recursively.  But there are still spots that want to use it for
@@ -767,29 +598,89 @@ void FGNasalSys::update(double)
     _context = naNewContext();
 }
 
+bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
+{
+  return p1.file() < p2.file();
+}
+
+// Loads all scripts in given directory 
+void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir)
+{
+    simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
+    // sort scripts, avoid loading sequence effects due to file system's
+    // random directory order
+    std::sort(scripts.begin(), scripts.end(), pathSortPredicate);
+
+    for (unsigned int i=0; i<scripts.size(); ++i) {
+      SGPath fullpath(scripts[i]);
+      SGPath file = fullpath.file();
+      loadModule(fullpath, file.base().c_str());
+    }
+}
+
+// Create module with list of scripts
+void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
+{
+    if (scripts.size()>0)
+    {
+        SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
+        SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
+        for (unsigned int i=0; i<scripts.size(); ++i) {
+            SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
+            pFileNode->setStringValue(scripts[i].c_str());
+        }
+        if (!module_node->hasChild("enabled",0))
+        {
+            SGPropertyNode* node = module_node->getChild("enabled",0,true);
+            node->setBoolValue(true);
+            node->setAttribute(SGPropertyNode::USERARCHIVE,true);
+        }
+    }
+}
+
 // Loads the scripts found under /nasal in the global tree
 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);
+        loadPropertyScripts(n);
+    }
+}
 
-        const char* module = n->getName();
-        if(n->hasChild("module"))
-            module = n->getStringValue("module");
+// 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 = globals->resolve_maybe_aircraft_path(file);
-            loadModule(p, module);
+            SGPath p(file);
+            if (!p.isAbsolute() || !p.exists())
+            {
+                p = globals->resolve_maybe_aircraft_path(file);
+                if (p.isNull())
+                {
+                    SG_LOG(SG_NASAL, SG_ALERT, "Cannot find Nasal script '" <<
+                            file << "' for module '" << module << "'.");
+                }
+            }
+            ok &= p.isNull() ? false : loadModule(p, module);
             j++;
         }
 
@@ -799,10 +690,32 @@ void FGNasalSys::loadPropertyScripts()
             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);
+                    "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);
+        }
+    }
+    SGPropertyNode* loaded = n->getChild("loaded",0,true);
+    loaded->setAttribute(SGPropertyNode::PRESERVE,true);
+    loaded->setBoolValue(is_loaded);
 }
 
 // Logs a runtime error, with stack trace, to the FlightGear log stream
@@ -822,7 +735,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);
@@ -830,25 +743,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
@@ -863,6 +777,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)
@@ -1018,7 +933,7 @@ naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
     FGNasalListener *nl = new FGNasalListener(node, code, this,
             gcSave(code), _listenerId, init, type);
 
-    node->addChangeListener(nl, init);
+    node->addChangeListener(nl, init != 0);
 
     _listener[_listenerId] = nl;
     return naNum(_listenerId++);
@@ -1149,36 +1064,26 @@ bool FGNasalListener::changed(SGPropertyNode* node)
 
 unsigned int FGNasalModelData::_module_id = 0;
 
-void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
-                                   osg::Node *)
+void FGNasalModelData::load()
 {
-    if(!prop)
-        return;
-    SGPropertyNode *nasal = prop->getNode("nasal");
-    if(!nasal)
-        return;
-
-    SGPropertyNode *load = nasal->getNode("load");
-    _unload = nasal->getNode("unload");
-    if(!load && !_unload)
-        return;
-
     std::stringstream m;
     m << "__model" << _module_id++;
     _module = m.str();
 
-    const char *s = load ? load->getStringValue() : "";
+    SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str());
+
+    const char *s = _load ? _load->getStringValue() : "";
 
     naRef arg[2];
     arg[0] = nasalSys->propNodeGhost(_root);
-    arg[1] = nasalSys->propNodeGhost(prop);
-    nasalSys->createModule(_module.c_str(), path.c_str(), s, strlen(s),
+    arg[1] = nasalSys->propNodeGhost(_prop);
+    nasalSys->createModule(_module.c_str(), _path.c_str(), s, strlen(s),
                            _root, 2, arg);
 }
 
-FGNasalModelData::~FGNasalModelData()
+void FGNasalModelData::unload()
 {
-    if(_module.empty())
+    if (_module.empty())
         return;
 
     if(!nasalSys) {
@@ -1187,14 +1092,52 @@ FGNasalModelData::~FGNasalModelData()
         return;
     }
 
-    if(_unload) {
+    SG_LOG(SG_NASAL, SG_DEBUG, "Unloading nasal module " << _module.c_str());
+
+    if (_unload)
+    {
         const char *s = _unload->getStringValue();
         nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root);
     }
+
     nasalSys->deleteModule(_module.c_str());
 }
 
+void FGNasalModelDataProxy::modelLoaded(const string& path, SGPropertyNode *prop,
+                                   osg::Node *)
+{
+    if(!nasalSys) {
+        SG_LOG(SG_NASAL, SG_WARN, "Trying to run a <load> script "
+                "without Nasal subsystem present.");
+        return;
+    }
+
+    if(!prop)
+        return;
+
+    SGPropertyNode *nasal = prop->getNode("nasal");
+    if(!nasal)
+        return;
 
+    SGPropertyNode* load   = nasal->getNode("load");
+    SGPropertyNode* unload = nasal->getNode("unload");
+
+    if ((!load) && (!unload))
+        return;
+
+    _data = new FGNasalModelData(_root, path, prop, load, unload);
+
+    // register Nasal module to be created and loaded in the main thread.
+    nasalSys->registerToLoad(_data);
+}
+
+FGNasalModelDataProxy::~FGNasalModelDataProxy()
+{
+    // when necessary, register Nasal module to be destroyed/unloaded
+    // in the main thread.
+    if ((_data.valid())&&(nasalSys))
+        nasalSys->registerToUnload(_data);
+}
 
 // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
 //