#include <sys/types.h>
#include <sys/stat.h>
#include <fstream>
-
-#include <plib/ul.h>
+#include <sstream>
+#include <algorithm> // for std::sort
#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 "NasalCanvas.hxx"
+#include "NasalClipboard.hxx"
+#include "NasalCondition.hxx"
+
#include <Main/globals.hxx>
-#include <Main/fg_props.hxx>
#include <Main/util.hxx>
-#include <Scenery/scenery.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
_callCount = 0;
}
+// Utility. Sets a named key in a hash by C string, rather than nasal
+// string object.
+void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
+{
+ naRef s = naNewString(_context);
+ naStr_fromdata(s, (char*)key, strlen(key));
+ naHash_set(hash, s, val);
+}
+
+void FGNasalSys::globalsSet(const char* key, naRef val)
+{
+ hashset(_globals, key, val);
+}
+
+naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
+{
+ return callMethod(code, naNil(), argc, args, locals);
+}
+
// Does a naCall() in a new context. Wrapped here to make lock
// tracking easier. Extension functions are called with the lock, but
// we have to release it before making a new naCall(). So rather than
// drop the lock in every extension function that might call back into
// Nasal, we keep a stack depth counter here and only unlock/lock
// around the naCall if it isn't the first one.
-naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
+
+naRef FGNasalSys::callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals)
{
naContext ctx = naNewContext();
if(_callCount) naModUnlock();
_callCount++;
- naRef result = naCall(ctx, code, argc, args, naNil(), locals);
+ naRef result = naCall(ctx, code, argc, args, self, locals);
if(naGetError(ctx))
logError(ctx);
_callCount--;
return script;
}
-// Utility. Sets a named key in a hash by C string, rather than nasal
-// string object.
-void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
-{
- naRef s = naNewString(_context);
- naStr_fromdata(s, (char*)key, strlen(key));
- naHash_set(hash, s, val);
-}
-
// The get/setprop functions accept a *list* of strings and walk
// through the property tree with them to find the appropriate node.
// This allows a Nasal object to hold onto a property path and use it
// nil if it doesn't exist.
static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
{
+ using namespace simgear;
const SGPropertyNode* p = findnode(c, args, argc);
if(!p) return naNil();
switch(p->getType()) {
- case SGPropertyNode::BOOL: case SGPropertyNode::INT:
- case SGPropertyNode::LONG: case SGPropertyNode::FLOAT:
- case SGPropertyNode::DOUBLE:
- return naNum(p->getDoubleValue());
-
- case SGPropertyNode::STRING:
- case SGPropertyNode::UNSPECIFIED:
+ case props::BOOL: case props::INT:
+ case props::LONG: case props::FLOAT:
+ case props::DOUBLE:
+ {
+ 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:
{
naRef nastr = naNewString(c);
const char* val = p->getStringValue();
naStr_fromdata(nastr, (char*)val, strlen(val));
return nastr;
}
- case SGPropertyNode::ALIAS: // <--- FIXME, recurse?
+ case props::ALIAS: // <--- FIXME, recurse?
default:
return naNil();
}
buf[BUFLEN] = 0;
char* p = buf;
int buflen = BUFLEN;
+ if(argc < 2) naRuntimeError(c, "setprop() expects at least 2 arguments");
for(int i=0; i<argc-1; i++) {
naRef s = naStringValue(c, args[i]);
if(naIsNil(s)) return naNil();
SGPropertyNode* props = globals->get_props();
naRef val = args[argc-1];
+ bool result = false;
try {
- bool r;
- if(naIsString(val)) r = props->setStringValue(buf, naStr_data(val));
+ if(naIsString(val)) result = props->setStringValue(buf, naStr_data(val));
else {
naRef n = naNumValue(val);
if(naIsNil(n))
naRuntimeError(c, "setprop() value is not string or number");
- r = props->setDoubleValue(buf, n.num);
+
+ if (osg::isNaN(n.num)) {
+ naRuntimeError(c, "setprop() passed a NaN");
+ }
+
+ result = props->setDoubleValue(buf, n.num);
}
- if(!r) naRuntimeError(c, "setprop(): property is not writable");
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
}
- return naNil();
+ return naNum(result);
#undef BUFLEN
}
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());
}
{
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;
}
+/**
+ * Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory
+ */
+static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
+{
+ if(argc != 1 || !naIsString(args[0]))
+ naRuntimeError(c, "bad arguments to resolveDataPath()");
+
+ SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
+ const char* pdata = p.c_str();
+ return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
+}
+
// Parse XML file.
// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
//
// Return UNIX epoch time in seconds.
static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
{
-#ifdef WIN32
+#ifdef _WIN32
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
// 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) naRuntimeError(c, "geodinfo() expects 2 arguments: lat, lon");
- double lat = naNumValue(args[0]).num;
- double lon = naNumValue(args[1]).num;
- double elev;
- const SGMaterial *mat;
- if(!globals->get_scenery()->get_elevation_m(lat, lon, 10000.0, 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) {
- // fine, just 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) {
- naRuntimeError(c, "airportinfo() couldn't find airport:%s", s);
- 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()));
-#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 },
{ "srand", f_srand },
{ "abort", f_abort },
{ "directory", f_directory },
+ { "resolvepath", f_resolveDataPath },
{ "parsexml", f_parsexml },
{ "systime", f_systime },
- { "carttogeod", f_carttogeod },
- { "geodtocart", f_geodtocart },
- { "geodinfo", f_geodinfo },
- { "airportinfo", f_airportinfo },
{ 0, 0 }
};
hashset(_globals, funcs[i].name,
naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
+
+
// And our SGPropertyNode wrapper
hashset(_globals, "props", genPropsModule());
_gcHash = naNewHash(_context);
hashset(_globals, "__gcsave", _gcHash);
+ initNasalPositioned(_globals, _context, _gcHash);
+ initNasalCanvas(_globals, _context, _gcHash);
+ NasalClipboard::init(this);
+ initNasalCondition(_globals, _context, _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";
SGPropertyNode *signal = fgGetNode("/sim/signals", true);
signal->setBoolValue(s, true);
- signal->removeChildren(s);
+ signal->removeChildren(s, false);
// Pull scripts out of the property tree, too
loadPropertyScripts();
+
+ // now Nasal modules are loaded, we can do some delayed work
+ postinitNasalPositioned(_globals, _context);
}
void FGNasalSys::update(double)
{
+ if( NasalClipboard::getInstance() )
+ NasalClipboard::getInstance()->update();
+
if(!_dead_listener.empty()) {
vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
for(it = _dead_listener.begin(); it != end; ++it) delete *it;
_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
_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");
-
- // allow multiple files to be specified within in a single
+// 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->get_fg_root());
- p.append(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++;
}
- // Old code which only allowed a single file to be specified per module
- /*
- const char* file = n->getStringValue("file");
- if(!n->hasChild("file")) file = 0; // Hrm...
- if(file) {
- SGPath p(globals->get_fg_root());
- p.append(file);
- loadModule(p, module);
- }
- */
-
const char* src = n->getStringValue("script");
if(!n->hasChild("script")) src = 0; // Hrm...
if(src)
- createModule(module, n->getPath(), src, strlen(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);
+ "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
// 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);
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
call(code, argc, args, locals);
hashset(_globals, moduleName, locals);
+ return true;
}
void FGNasalSys::deleteModule(const char* moduleName)
return naBindFunction(_context, code, _globals);
}
-bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
+bool FGNasalSys::handleCommand( const char* moduleName,
+ const char* fileName,
+ const char* src,
+ const SGPropertyNode* arg )
{
- const char* nasal = arg->getStringValue("script");
- const char* moduleName = arg->getStringValue("module");
- naRef code = parse(arg->getPath(true), nasal, strlen(nasal));
+ naRef code = parse(fileName, src, strlen(src));
if(naIsNil(code)) return false;
// Commands can be run "in" a module. Make sure that module
return true;
}
+bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
+{
+ const char* src = arg->getStringValue("script");
+ const char* moduleName = arg->getStringValue("module");
+
+ return handleCommand( moduleName,
+ arg ? arg->getPath(true).c_str() : moduleName,
+ src,
+ arg );
+}
+
// settimer(func, dt, simtime) extension function. The first argument
// is a Nasal function to call, the second is a delta time (from now),
// in seconds. The third, if present, is a boolean value indicating
return naNil();
}
- int type = argc > 3 && naIsNum(args[3]) ? (int)args[3].num : 1;
+ int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
+ int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
FGNasalListener *nl = new FGNasalListener(node, code, this,
- gcSave(code), _listenerId, type);
+ gcSave(code), _listenerId, init, type);
- bool initial = argc > 2 && naTrue(args[2]);
- node->addChangeListener(nl, initial);
+ node->addChangeListener(nl, init != 0);
_listener[_listenerId] = nl;
return naNum(_listenerId++);
// FGNasalListener class.
FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
- FGNasalSys* nasal, int key, int id, int type) :
+ FGNasalSys* nasal, int key, int id,
+ int init, int type) :
_node(node),
_code(code),
_gcKey(key),
_id(id),
_nas(nasal),
+ _init(init),
_type(type),
_active(0),
_dead(false),
- _first_call(true),
_last_int(0L),
_last_float(0.0)
{
+ if(_type == 0 && !_init)
+ changed(node);
}
FGNasalListener::~FGNasalListener()
void FGNasalListener::valueChanged(SGPropertyNode* node)
{
if(_type < 2 && node != _node) return; // skip child events
- if(_type > 0 || changed(_node) || _first_call)
+ if(_type > 0 || changed(_node) || _init)
call(node, naNum(0));
- _first_call = false;
+ _init = 0;
}
void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
bool FGNasalListener::changed(SGPropertyNode* node)
{
- SGPropertyNode::Type type = node->getType();
- if(type == SGPropertyNode::NONE) return false;
- if(type == SGPropertyNode::UNSPECIFIED) return true;
+ using namespace simgear;
+ props::Type type = node->getType();
+ if(type == props::NONE) return false;
+ if(type == props::UNSPECIFIED) return true;
bool result;
switch(type) {
- case SGPropertyNode::BOOL:
- case SGPropertyNode::INT:
- case SGPropertyNode::LONG:
+ case props::BOOL:
+ case props::INT:
+ case props::LONG:
{
long l = node->getLongValue();
result = l != _last_int;
_last_int = l;
return result;
}
- case SGPropertyNode::FLOAT:
- case SGPropertyNode::DOUBLE:
+ case props::FLOAT:
+ case props::DOUBLE:
{
double d = node->getDoubleValue();
result = d != _last_float;
// destructor the <unload> script. The latter happens when the model branch
// is removed from the scene graph.
-void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
- osg::Node *)
+unsigned int FGNasalModelData::_module_id = 0;
+
+void FGNasalModelData::load()
{
- if(!prop)
- return;
- SGPropertyNode *nasal = prop->getNode("nasal");
- if(!nasal)
- return;
+ std::stringstream m;
+ m << "__model" << _module_id++;
+ _module = m.str();
- SGPropertyNode *load = nasal->getNode("load");
- _unload = nasal->getNode("unload");
- if(!load && !_unload)
- return;
+ SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str());
- _module = path;
- if(_props)
- _module += ':' + _props->getPath();
- const char *s = load ? load->getStringValue() : "";
+ const char *s = _load ? _load->getStringValue() : "";
naRef arg[2];
arg[0] = nasalSys->propNodeGhost(_root);
- arg[1] = nasalSys->propNodeGhost(prop);
- nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s),
+ arg[1] = nasalSys->propNodeGhost(_prop);
+ nasalSys->createModule(_module.c_str(), _path.c_str(), s, strlen(s),
_root, 2, arg);
- _props = 0;
}
-FGNasalModelData::~FGNasalModelData()
+void FGNasalModelData::unload()
{
- if(_module.empty())
+ if (_module.empty())
return;
if(!nasalSys) {
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()
//