# include "config.h"
#endif
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h> // gettimeofday
#endif
#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/interpolator.hxx>
-#include <simgear/scene/material/mat.hxx>
+#include <simgear/misc/sg_dir.hxx>
+#include <simgear/misc/SimpleMarkdown.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/structure/event_mgr.hxx>
+#include <simgear/debug/BufferedLogCallback.hxx>
+#include <simgear/package/md5.h>
+
+#include <simgear/nasal/cppbind/from_nasal.hxx>
+#include <simgear/nasal/cppbind/to_nasal.hxx>
+#include <simgear/nasal/cppbind/Ghost.hxx>
+#include <simgear/nasal/cppbind/NasalHash.hxx>
+
+#include "NasalSGPath.hxx"
+#include "NasalSys.hxx"
+#include "NasalSys_private.hxx"
+#include "NasalAircraft.hxx"
+#include "NasalModelData.hxx"
+#include "NasalPositioned.hxx"
+#include "NasalCanvas.hxx"
+#include "NasalClipboard.hxx"
+#include "NasalCondition.hxx"
+#include "NasalHTTP.hxx"
+#include "NasalString.hxx"
-#include <Airports/runways.hxx>
-#include <Airports/simple.hxx>
#include <Main/globals.hxx>
-#include <Main/fg_props.hxx>
#include <Main/util.hxx>
-#include <Scenery/scenery.hxx>
-#include <Navaids/navrecord.hxx>
+#include <Main/fg_props.hxx>
-#include "NasalSys.hxx"
+using std::map;
+using std::string;
+using std::vector;
+
+void postinitNasalGUI(naRef globals, naContext c);
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);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+
+class TimerObj : public SGReferenced
+{
+public:
+ TimerObj(FGNasalSys* sys, naRef f, naRef self, double interval) :
+ _sys(sys),
+ _func(f),
+ _self(self),
+ _isRunning(false),
+ _interval(interval),
+ _singleShot(false)
+ {
+ char nm[128];
+ snprintf(nm, 128, "nasal-timer-%p", this);
+ _name = nm;
+ _gcRoot = sys->gcSave(f);
+ _gcSelf = sys->gcSave(self);
+ }
+
+ virtual ~TimerObj()
+ {
+ stop();
+ _sys->gcRelease(_gcRoot);
+ _sys->gcRelease(_gcSelf);
+ }
+
+ bool isRunning() const { return _isRunning; }
+
+ void stop()
+ {
+ if (_isRunning) {
+ globals->get_event_mgr()->removeTask(_name);
+ _isRunning = false;
+ }
+ }
+
+ void start()
+ {
+ if (_isRunning) {
+ return;
+ }
+
+ _isRunning = true;
+ if (_singleShot) {
+ globals->get_event_mgr()->addEvent(_name, this, &TimerObj::invoke, _interval);
+ } else {
+ globals->get_event_mgr()->addTask(_name, this, &TimerObj::invoke,
+ _interval, _interval /* delay */);
+ }
+ }
+
+ // stop and then start -
+ void restart(double newInterval)
+ {
+ _interval = newInterval;
+ stop();
+ start();
+ }
+
+ void invoke()
+ {
+ naRef *args = NULL;
+ _sys->callMethod(_func, _self, 0, args, naNil() /* locals */);
+ if (_singleShot) {
+ _isRunning = false;
+ }
+ }
+
+ void setSingleShot(bool aSingleShot)
+ {
+ _singleShot = aSingleShot;
+ }
+
+ bool isSingleShot() const
+ { return _singleShot; }
+private:
+ std::string _name;
+ FGNasalSys* _sys;
+ naRef _func, _self;
+ int _gcRoot, _gcSelf;
+ bool _isRunning;
+ double _interval;
+ bool _singleShot;
+};
+
+typedef SGSharedPtr<TimerObj> TimerObjRef;
+typedef nasal::Ghost<TimerObjRef> NasalTimerObj;
+
+///////////////////////////////////////////////////////////////////////////
// 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
return buf;
}
-FGNasalSys::FGNasalSys()
+FGNasalSys::FGNasalSys() :
+ _inited(false)
{
nasalSys = this;
_context = 0;
_globals = naNil();
- _gcHash = naNil();
- _nextGCKey = 0; // Any value will do
- _callCount = 0;
+ _string = naNil();
+ _wrappedNodeFunc = naNil();
+
+ _log = new simgear::BufferedLogCallback(SG_NASAL, SG_INFO);
+ _log->truncateAt(255);
+ sglog().addCallback(_log);
+
+ naSetErrorHandler(&logError);
+}
+
+// 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);
+}
+
+naRef FGNasalSys::callWithContext(naContext ctx, naRef code, int argc, naRef* args, naRef locals)
+{
+ return callMethodWithContext(ctx, code, naNil(), argc, args, locals);
}
// Does a naCall() in a new context. Wrapped here to make lock
// 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);
- if(naGetError(ctx))
- logError(ctx);
- _callCount--;
- if(_callCount) naModLock();
- naFreeContext(ctx);
- return result;
+ return naCallMethod(code, self, argc, args, locals);
+}
+
+naRef FGNasalSys::callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef* args, naRef locals)
+{
+ return naCallMethodCtx(ctx, code, self, argc, args, locals);
}
FGNasalSys::~FGNasalSys()
{
+ if (_inited) {
+ SG_LOG(SG_GENERAL, SG_ALERT, "Nasal was not shutdown");
+ }
nasalSys = 0;
- map<int, FGNasalListener *>::iterator it, end = _listener.end();
- for(it = _listener.begin(); it != end; ++it)
- delete it->second;
-
- naFreeContext(_context);
- _globals = naNil();
}
bool FGNasalSys::parseAndRun(const char* sourceCode)
{
- naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
+ naContext ctx = naNewContext();
+ naRef code = parse(ctx, "FGNasalSys::parseAndRun()", sourceCode,
strlen(sourceCode));
- if(naIsNil(code))
+ if(naIsNil(code)) {
+ naFreeContext(ctx);
return false;
- call(code, 0, 0, naNil());
+ }
+ callWithContext(ctx, code, 0, 0, naNil());
+ naFreeContext(ctx);
return true;
}
+#if 0
FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
{
FGNasalScript* script = new FGNasalScript();
script->_gcKey = gcSave(script->_code);
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);
-}
+#endif
// 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
// like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02). This
// is the utility function that walks the property tree.
-// Future enhancement: support integer arguments to specify array
-// elements.
-static SGPropertyNode* findnode(naContext c, naRef* vec, int len)
+static SGPropertyNode* findnode(naContext c, naRef* vec, int len, bool create=false)
{
SGPropertyNode* p = globals->get_props();
try {
for(int i=0; i<len; i++) {
naRef a = vec[i];
- if(!naIsString(a)) return 0;
- p = p->getNode(naStr_data(a));
+ if(!naIsString(a)) {
+ naRuntimeError(c, "bad argument to setprop/getprop path: expected a string");
+ }
+ naRef b = i < len-1 ? naNumValue(vec[i+1]) : naNil();
+ if (!naIsNil(b)) {
+ p = p->getNode(naStr_data(a), (int)b.num, create);
+ i++;
+ } else {
+ p = p->getNode(naStr_data(a), create);
+ }
if(p == 0) return 0;
}
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
- return 0;
}
return p;
}
static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
- const SGPropertyNode* p = findnode(c, args, argc);
+ if (argc < 1) {
+ naRuntimeError(c, "getprop() expects at least 1 argument");
+ }
+ const SGPropertyNode* p = findnode(c, args, argc, false);
if(!p) return naNil();
switch(p->getType()) {
case props::BOOL: case props::INT:
case props::LONG: case props::FLOAT:
case props::DOUBLE:
- return naNum(p->getDoubleValue());
-
+ {
+ double dv = p->getDoubleValue();
+ if (SGMisc<double>::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:
{
// final argument.
static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
{
-#define BUFLEN 1024
- char buf[BUFLEN + 1];
- buf[BUFLEN] = 0;
- char* p = buf;
- int buflen = BUFLEN;
- for(int i=0; i<argc-1; i++) {
- naRef s = naStringValue(c, args[i]);
- if(naIsNil(s)) return naNil();
- strncpy(p, naStr_data(s), buflen);
- p += naStr_len(s);
- buflen = BUFLEN - (p - buf);
- if(i < (argc-2) && buflen > 0) {
- *p++ = '/';
- buflen--;
- }
+ if (argc < 2) {
+ naRuntimeError(c, "setprop() expects at least 2 arguments");
}
+ naRef val = args[argc - 1];
+ SGPropertyNode* p = findnode(c, args, argc-1, true);
- SGPropertyNode* props = globals->get_props();
- naRef val = args[argc-1];
bool result = false;
try {
- if(naIsString(val)) result = props->setStringValue(buf, naStr_data(val));
+ if(naIsString(val)) result = p->setStringValue(naStr_data(val));
else {
- naRef n = naNumValue(val);
- if(naIsNil(n))
+ if(!naIsNum(val))
naRuntimeError(c, "setprop() value is not string or number");
- result = props->setDoubleValue(buf, n.num);
+
+ if (SGMisc<double>::isNaN(val.num)) {
+ naRuntimeError(c, "setprop() passed a NaN");
+ }
+
+ result = p->setDoubleValue(val.num);
}
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
}
return naNum(result);
-#undef BUFLEN
}
// print() extension function. Concatenates and prints its arguments
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());
}
+// logprint() extension function. Same as above, all arguments after the
+// first argument are concatenated. Argument 0 is the log-level, matching
+// sgDebugPriority.
+static naRef f_logprint(naContext c, naRef me, int argc, naRef* args)
+{
+ if (argc < 1)
+ naRuntimeError(c, "no prioirty argument to logprint()");
+
+ naRef priority = args[0];
+ string buf;
+ int n = argc;
+ for(int i=1; i<n; i++) {
+ naRef s = naStringValue(c, args[i]);
+ if(naIsNil(s)) continue;
+ buf += naStr_data(s);
+ }
+// use the nasal source file and line for the message location, since
+// that's more useful than the location here!
+ sglog().log(SG_NASAL, (sgDebugPriority)(int) priority.num,
+ naStr_data(naGetSourceFile(c, 0)),
+ naGetLine(c, 0), buf);
+ return naNum(buf.length());
+}
+
+
// fgcommand() extension function. Executes a named command via the
// FlightGear command manager. Takes a single property node name as
// an argument.
return naNil();
}
+static naRef f_makeTimer(naContext c, naRef me, int argc, naRef* args)
+{
+ if (!naIsNum(args[0])) {
+ naRuntimeError(c, "bad interval argument to maketimer");
+ }
+
+ naRef func, self = naNil();
+ if (naIsFunc(args[1])) {
+ func = args[1];
+ } else if ((argc == 3) && naIsFunc(args[2])) {
+ self = args[1];
+ func = args[2];
+ }
+
+ TimerObj* timerObj = new TimerObj(nasalSys, func, self, args[0].num);
+ return nasal::to_nasal(c, timerObj);
+}
+
// setlistener(func, property, bool) extension function. Falls through to
// FGNasalSys::setListener(). See there for docs.
static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
// value/delta numbers.
static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
{
- SGPropertyNode* node;
- naRef prop = argc > 0 ? args[0] : naNil();
- if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
- else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
- else return naNil();
-
- naRef curve = argc > 1 ? args[1] : naNil();
- if(!naIsVector(curve)) return naNil();
- int nPoints = naVec_size(curve) / 2;
- double* values = new double[nPoints];
- double* deltas = new double[nPoints];
- for(int i=0; i<nPoints; i++) {
- values[i] = naNumValue(naVec_get(curve, 2*i)).num;
- deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num;
- }
-
- ((SGInterpolator*)globals->get_subsystem_mgr()
- ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator"))
- ->interpolate(node, nPoints, values, deltas);
-
- delete[] values;
- delete[] deltas;
- return naNil();
+ SGPropertyNode* node;
+ naRef prop = argc > 0 ? args[0] : naNil();
+ if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
+ else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
+ else return naNil();
+
+ naRef curve = argc > 1 ? args[1] : naNil();
+ if(!naIsVector(curve)) return naNil();
+ int nPoints = naVec_size(curve) / 2;
+
+ simgear::PropertyList value_nodes;
+ value_nodes.reserve(nPoints);
+ double_list deltas;
+ deltas.reserve(nPoints);
+
+ for( int i = 0; i < nPoints; ++i )
+ {
+ SGPropertyNode* val = new SGPropertyNode;
+ val->setDoubleValue(naNumValue(naVec_get(curve, 2*i)).num);
+ value_nodes.push_back(val);
+ deltas.push_back(naNumValue(naVec_get(curve, 2*i+1)).num);
+ }
+
+ node->interpolate("numeric", value_nodes, deltas, "linear");
+ return naNil();
}
// This is a better RNG than the one in the default Nasal distribution
{
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));
+}
+
+static naRef f_findDataDir(naContext c, naRef me, int argc, naRef* args)
+{
+ if(argc != 1 || !naIsString(args[0]))
+ naRuntimeError(c, "bad arguments to findDataDir()");
+
+ SGPath p = globals->find_data_dir(naStr_data(args[0]));
+ const char* pdata = p.c_str();
+ return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
+}
+
+class NasalCommand : public SGCommandMgr::Command
+{
+public:
+ NasalCommand(FGNasalSys* sys, naRef f, const std::string& name) :
+ _sys(sys),
+ _func(f),
+ _name(name)
+ {
+ globals->get_commands()->addCommandObject(_name, this);
+ _gcRoot = sys->gcSave(f);
+ }
+
+ virtual ~NasalCommand()
+ {
+ _sys->gcRelease(_gcRoot);
+ }
+
+ virtual bool operator()(const SGPropertyNode* aNode)
+ {
+ _sys->setCmdArg(const_cast<SGPropertyNode*>(aNode));
+ naRef args[1];
+ args[0] = _sys->wrappedPropsNode(const_cast<SGPropertyNode*>(aNode));
+
+ _sys->callMethod(_func, naNil(), 1, args, naNil() /* locals */);
+
+ return true;
+ }
+
+private:
+ FGNasalSys* _sys;
+ naRef _func;
+ int _gcRoot;
+ std::string _name;
+};
+
+static naRef f_addCommand(naContext c, naRef me, int argc, naRef* args)
+{
+ if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1]))
+ naRuntimeError(c, "bad arguments to addcommand()");
+
+ nasalSys->addCommand(args[1], naStr_data(args[0]));
+ return naNil();
+}
+
+static naRef f_removeCommand(naContext c, naRef me, int argc, naRef* args)
+{
+ if ((argc < 1) || !naIsString(args[0]))
+ naRuntimeError(c, "bad argument to removecommand()");
+
+ globals->get_commands()->removeCommand(naStr_data(args[0]));
+ return naNil();
+}
+
// Parse XML file.
// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
//
return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
}
+/**
+ * Parse very simple and small subset of markdown
+ *
+ * parse_markdown(src)
+ */
+static naRef f_parse_markdown(naContext c, naRef me, int argc, naRef* args)
+{
+ nasal::CallContext ctx(c, argc, args);
+ return ctx.to_nasal(
+ simgear::SimpleMarkdown::parse(ctx.requireArg<std::string>(0))
+ );
+}
+
+/**
+ * Create md5 hash from given string
+ *
+ * md5(str)
+ */
+static naRef f_md5(naContext c, naRef me, int argc, naRef* args)
+{
+ nasal::CallContext ctx(c, argc, args);
+ std::string const str = ctx.requireArg<std::string>(0);
+
+ SG_MD5_CTX md5_ctx;
+ SG_MD5Init(&md5_ctx);
+ SG_MD5Update(&md5_ctx, (unsigned char*)str.c_str(), str.size());
+
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ SG_MD5Final(digest, &md5_ctx);
+
+ // TODO make something more generic
+ // convert final sum to hex
+ const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ std::stringstream hexMd5;
+ for (int i=0; i<MD5_DIGEST_LENGTH;++i) {
+ hexMd5 << hexChar[digest[i] >> 4];
+ hexMd5 << hexChar[digest[i] & 0x0f];
+ }
+
+ return ctx.to_nasal(hexMd5.str());
+}
+
// Return UNIX epoch time in seconds.
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));
- }
-
-#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 },
{ "setprop", f_setprop },
{ "print", f_print },
+ { "logprint", f_logprint },
{ "_fgcommand", f_fgcommand },
{ "settimer", f_settimer },
+ { "maketimer", f_makeTimer },
{ "_setlistener", f_setlistener },
{ "removelistener", f_removelistener },
+ { "addcommand", f_addCommand },
+ { "removecommand", f_removeCommand },
{ "_cmdarg", f_cmdarg },
{ "_interpolate", f_interpolate },
{ "rand", f_rand },
{ "srand", f_srand },
{ "abort", f_abort },
{ "directory", f_directory },
+ { "resolvepath", f_resolveDataPath },
+ { "finddata", f_findDataDir },
{ "parsexml", f_parsexml },
+ { "parse_markdown", f_parse_markdown },
+ { "md5", f_md5 },
{ "systime", f_systime },
- { "carttogeod", f_carttogeod },
- { "geodtocart", f_geodtocart },
- { "geodinfo", f_geodinfo },
- { "airportinfo", f_airportinfo },
{ 0, 0 }
};
return propNodeGhost(_cmdArg);
}
+void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
+{
+ _cmdArg = aNode;
+}
+
void FGNasalSys::init()
{
+ if (_inited) {
+ SG_LOG(SG_GENERAL, SG_ALERT, "duplicate init of Nasal");
+ }
int i;
_context = naNewContext();
// And our SGPropertyNode wrapper
hashset(_globals, "props", genPropsModule());
- // Make a "__gcsave" hash to hold the naRef objects which get
- // passed to handles outside the interpreter (to protect them from
- // begin garbage-collected).
- _gcHash = naNewHash(_context);
- hashset(_globals, "__gcsave", _gcHash);
+ // Add string methods
+ _string = naInit_string(_context);
+ naSave(_context, _string);
+ initNasalString(_globals, _string, _context);
+
+ initNasalPositioned(_globals, _context);
+ initNasalPositioned_cppbind(_globals, _context);
+ initNasalAircraft(_globals, _context);
+ NasalClipboard::init(this);
+ initNasalCanvas(_globals, _context);
+ initNasalCondition(_globals, _context);
+ initNasalHTTP(_globals, _context);
+ initNasalSGPath(_globals, _context);
+
+ NasalTimerObj::init("Timer")
+ .method("start", &TimerObj::start)
+ .method("stop", &TimerObj::stop)
+ .method("restart", &TimerObj::restart)
+ .member("singleShot", &TimerObj::isSingleShot, &TimerObj::setSingleShot)
+ .member("isRunning", &TimerObj::isRunning);
// 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, false);
+ signal->removeChildren(s);
// Pull scripts out of the property tree, too
loadPropertyScripts();
+
+ // now Nasal modules are loaded, we can do some delayed work
+ postinitNasalPositioned(_globals, _context);
+ postinitNasalGUI(_globals, _context);
+
+ _inited = true;
+}
+
+void FGNasalSys::shutdown()
+{
+ if (!_inited) {
+ return;
+ }
+
+ shutdownNasalPositioned();
+
+ map<int, FGNasalListener *>::iterator it, end = _listener.end();
+ for(it = _listener.begin(); it != end; ++it)
+ delete it->second;
+ _listener.clear();
+
+ NasalCommandDict::iterator j = _commands.begin();
+ for (; j != _commands.end(); ++j) {
+ globals->get_commands()->removeCommand(j->first);
+ }
+ _commands.clear();
+
+ std::vector<FGNasalModuleListener*>::iterator k = _moduleListeners.begin();
+ for(; k!= _moduleListeners.end(); ++k)
+ delete *k;
+ _moduleListeners.clear();
+
+ naClearSaved();
+
+ _string = naNil(); // will be freed by _context
+ naFreeContext(_context);
+
+ //setWatchedRef(_globals);
+
+ // remove the recursive reference in globals
+ hashset(_globals, "globals", naNil());
+ _globals = naNil();
+
+ naGC();
+ _inited = false;
+}
+
+naRef FGNasalSys::wrappedPropsNode(SGPropertyNode* aProps)
+{
+ if (naIsNil(_wrappedNodeFunc)) {
+ nasal::Hash props = getGlobals().get<nasal::Hash>("props");
+ _wrappedNodeFunc = props.get("wrapNode");
+ }
+
+ naRef args[1];
+ args[0] = propNodeGhost(aProps);
+ naContext ctx = naNewContext();
+ naRef wrapped = naCall(ctx, _wrappedNodeFunc, 1, args, naNil(), naNil());
+ naFreeContext(ctx);
+ return wrapped;
}
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())
+ {
+ if( _delay_load )
+ _delay_load = false;
+ else
+ // 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();
+ }
+
+ // Destroy all queued ghosts
+ nasal::ghostProcessDestroyList();
+
// 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");
+ // Note: simgear::Dir already reports file entries in a deterministic order,
+ // so a fixed loading sequence is guaranteed (same for every user)
+ 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.empty())
+ {
+ 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->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++;
}
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);
+ _moduleListeners.push_back(listener);
+ 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
void FGNasalSys::logError(naContext context)
{
- SG_LOG(SG_NASAL, SG_ALERT,
- "Nasal runtime error: " << naGetError(context));
+ SG_LOG(SG_NASAL, SG_ALERT, "Nasal runtime error: " << naGetError(context));
+ int stack_depth = naStackDepth(context);
+ if( stack_depth < 1 )
+ return;
SG_LOG(SG_NASAL, SG_ALERT,
" at " << naStr_data(naGetSourceFile(context, 0)) <<
", line " << naGetLine(context, 0));
- for(int i=1; i<naStackDepth(context); i++)
+ for(int i=1; i<stack_depth; i++)
SG_LOG(SG_NASAL, SG_ALERT,
" called from: " << naStr_data(naGetSourceFile(context, i)) <<
", line " << naGetLine(context, i));
// 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;
+ naContext ctx = naNewContext();
+ naRef code = parse(ctx, fileName, src, len);
+ if(naIsNil(code)) {
+ naFreeContext(ctx);
+ 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
// module. Make a new one if necessary.
naRef locals;
- naRef modname = naNewString(_context);
+ naRef modname = naNewString(ctx);
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
if(!naHash_get(_globals, modname, &locals))
- locals = naNewHash(_context);
+ locals = naNewHash(ctx);
_cmdArg = (SGPropertyNode*)cmdarg;
- call(code, argc, args, locals);
+ callWithContext(ctx, code, argc, args, locals);
hashset(_globals, moduleName, locals);
+
+ naFreeContext(ctx);
+ return true;
}
void FGNasalSys::deleteModule(const char* moduleName)
{
- naRef modname = naNewString(_context);
+ if (!_inited) {
+ // can occur on shutdown due to us being shutdown first, but other
+ // subsystems having Nasal objects.
+ return;
+ }
+
+ naContext ctx = naNewContext();
+ naRef modname = naNewString(ctx);
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
naHash_delete(_globals, modname);
+ naFreeContext(ctx);
}
-naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
+naRef FGNasalSys::parse(naContext ctx, const char* filename, const char* buf, int len)
{
int errLine = -1;
- naRef srcfile = naNewString(_context);
+ naRef srcfile = naNewString(ctx);
naStr_fromdata(srcfile, (char*)filename, strlen(filename));
- naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
+ naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine);
if(naIsNil(code)) {
SG_LOG(SG_NASAL, SG_ALERT,
- "Nasal parse error: " << naGetError(_context) <<
+ "Nasal parse error: " << naGetError(ctx) <<
" in "<< filename <<", line " << errLine);
return naNil();
}
// Bind to the global namespace before returning
- return naBindFunction(_context, code, _globals);
+ return naBindFunction(ctx, 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));
- if(naIsNil(code)) return false;
+ naContext ctx = naNewContext();
+ naRef code = parse(ctx, fileName, src, strlen(src));
+ if(naIsNil(code)) {
+ naFreeContext(ctx);
+ return false;
+ }
// Commands can be run "in" a module. Make sure that module
// exists, and set it up as the local variables hash for the
// command.
naRef locals = naNil();
if(moduleName[0]) {
- naRef modname = naNewString(_context);
+ naRef modname = naNewString(ctx);
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
if(!naHash_get(_globals, modname, &locals)) {
- locals = naNewHash(_context);
+ locals = naNewHash(ctx);
naHash_set(_globals, modname, locals);
}
}
// code doesn't need it.
_cmdArg = (SGPropertyNode*)arg;
- call(code, 0, 0, locals);
+ callWithContext(ctx, code, 0, 0, locals);
+ naFreeContext(ctx);
return true;
}
+bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
+{
+ const char* src = arg->getStringValue("script");
+ const char* moduleName = arg->getStringValue("module");
+
+ return handleCommand( moduleName,
+ arg->getPath(true).c_str(),
+ 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
int FGNasalSys::gcSave(naRef r)
{
- int key = _nextGCKey++;
- naHash_set(_gcHash, naNum(key), r);
- return key;
+ return naGCSave(r);
}
void FGNasalSys::gcRelease(int key)
{
- naHash_delete(_gcHash, naNum(key));
+ naGCRelease(key);
}
void FGNasalSys::NasalTimer::timerExpired()
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++);
return naNum(_listener.size());
}
+void FGNasalSys::registerToLoad(FGNasalModelData *data)
+{
+ if( _loadList.empty() )
+ _delay_load = true;
+ _loadList.push(data);
+}
+void FGNasalSys::registerToUnload(FGNasalModelData *data)
+{
+ _unloadList.push(data);
+}
+void FGNasalSys::addCommand(naRef func, const std::string& name)
+{
+ if (_commands.find(name) != _commands.end()) {
+ SG_LOG(SG_NASAL, SG_WARN, "duplicate add of command:" << name);
+ return;
+ }
+
+ NasalCommand* cmd = new NasalCommand(this, func, name);
+ _commands[name] = cmd;
+}
+
+void FGNasalSys::removeCommand(const std::string& name)
+{
+ NasalCommandDict::iterator it = _commands.find(name);
+ if (it == _commands.end()) {
+ SG_LOG(SG_NASAL, SG_WARN, "remove of unknwon command:" << name);
+ return;
+ }
+
+ // will delete the NasalCommand instance
+ globals->get_commands()->removeCommand(name);
+ _commands.erase(it);
+}
+
+//////////////////////////////////////////////////////////////////////////
// FGNasalListener class.
FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
void FGNasalListener::call(SGPropertyNode* which, naRef mode)
{
if(_active || _dead) return;
- SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
_active++;
naRef arg[4];
arg[0] = _nas->propNodeGhost(which);
}
}
-
-
-// FGNasalModelData class. If sgLoad3DModel() is called with a pointer to
-// such a class, then it lets modelLoaded() run the <load> script, and the
-// destructor the <unload> script. The latter happens when the model branch
-// is removed from the scene graph.
-
-unsigned int FGNasalModelData::_module_id = 0;
-
-void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
- osg::Node *)
-{
- 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() : "";
-
- naRef arg[2];
- arg[0] = nasalSys->propNodeGhost(_root);
- arg[1] = nasalSys->propNodeGhost(prop);
- nasalSys->createModule(_module.c_str(), path.c_str(), s, strlen(s),
- _root, 2, arg);
-}
-
-FGNasalModelData::~FGNasalModelData()
-{
- if(_module.empty())
- return;
-
- if(!nasalSys) {
- SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
- "without Nasal subsystem present.");
- return;
- }
-
- if(_unload) {
- const char *s = _unload->getStringValue();
- nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root);
- }
- nasalSys->deleteModule(_module.c_str());
-}
-
-
-
// NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
//
NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :