X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FScripting%2FNasalSys.cxx;h=75c49fbcf5d05d7b262b1de836e01ba9d21569d9;hb=4b59c152eafb5aa52c4562838090f037c07b65e9;hp=0134dcb264ee1a621fb324c26f4e7353add2857e;hpb=b82ea065c34c6ab4f13c76ec481d7ae37caab8b3;p=flightgear.git diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 0134dcb26..75c49fbcf 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -3,6 +3,10 @@ # include "config.h" #endif +#ifdef HAVE_WINDOWS_H +#include +#endif + #ifdef HAVE_SYS_TIME_H # include // gettimeofday #endif @@ -19,23 +23,38 @@ #include #include #include -#include -#include +#include #include #include #include +#include + +#include +#include +#include +#include + +#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 -#include #include
-#include
#include
-#include -#include -#include +#include
+using std::map; +using std::string; +using std::vector; -#include "NasalSys.hxx" +void postinitNasalGUI(naRef globals, naContext c); static FGNasalSys* nasalSys = 0; @@ -64,6 +83,100 @@ void FGNasalModuleListener::valueChanged(SGPropertyNode*) } } +////////////////////////////////////////////////////////////////////////// + + +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() + { + if( _singleShot ) + // Callback may restart the timer, so update status before callback is + // called (Prevent warnings of deleting not existing tasks from the + // event manager). + _isRunning = false; + + naRef *args = NULL; + _sys->callMethod(_func, _self, 0, args, naNil() /* locals */); + } + + 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 TimerObjRef; +typedef nasal::Ghost 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 @@ -93,14 +206,44 @@ static char* readfile(const char* file, int* lenOut) 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 @@ -109,41 +252,40 @@ FGNasalSys::FGNasalSys() // 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::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(); @@ -165,36 +307,33 @@ FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) 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; igetNode(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; } @@ -205,7 +344,10 @@ static SGPropertyNode* findnode(naContext c, naRef* vec, int len) 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()) { @@ -214,8 +356,8 @@ static naRef f_getprop(naContext c, naRef me, int argc, naRef* args) case props::DOUBLE: { double dv = p->getDoubleValue(); - if (osg::isNaN(dv)) { - SG_LOG(SG_GENERAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN"); + if (SGMisc::isNaN(dv)) { + SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN"); return naNil(); } @@ -241,45 +383,29 @@ static naRef f_getprop(naContext c, naRef me, int argc, naRef* args) // 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; - if(argc < 2) naRuntimeError(c, "setprop() expects at least 2 arguments"); - for(int i=0; i 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"); - if (osg::isNaN(n.num)) { + if (SGMisc::isNaN(val.num)) { naRuntimeError(c, "setprop() passed a NaN"); } - result = props->setDoubleValue(buf, n.num); + 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 @@ -294,10 +420,35 @@ 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()); } +// 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 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 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(); - ((SGInterpolator*)globals->get_subsystem_mgr() - ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator")) - ->interpolate(node, nPoints, values, deltas); + naRef curve = argc > 1 ? args[1] : naNil(); + if(!naIsVector(curve)) return naNil(); + int nPoints = naVec_size(curve) / 2; - delete[] values; - delete[] deltas; - return naNil(); + 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 @@ -429,6 +600,69 @@ static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args) return naStr_fromdata(naNewString(c), const_cast(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(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(aNode)); + naRef args[1]; + args[0] = _sys->wrappedPropsNode(const_cast(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( [, [, [, [, ]]]]); // @@ -467,6 +701,35 @@ static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args) return naStr_fromdata(naNewString(c), const_cast(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, me, argc, args); + return ctx.to_nasal( + simgear::SimpleMarkdown::parse(ctx.requireArg(0)) + ); +} + +/** + * Create md5 hash from given string + * + * md5(str) + */ +static naRef f_md5(naContext c, naRef me, int argc, naRef* args) +{ + if( argc != 1 || !naIsString(args[0]) ) + naRuntimeError(c, "md5(): wrong type or number of arguments"); + + return nasal::to_nasal( + c, + simgear::strutils::md5(naStr_data(args[0]), naStr_len(args[0])) + ); +} + // Return UNIX epoch time in seconds. static naRef f_systime(naContext c, naRef me, int argc, naRef* args) { @@ -483,314 +746,19 @@ static naRef f_systime(naContext c, naRef me, int argc, naRef* args) #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 n = mat->get_names(); - for(unsigned int i=0; i(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 , or nil -// on error. -// -// airportinfo(); e.g. "KSFO" -// airportinfo(); type := ("airport"|"seaport"|"heliport") -// airportinfo() same as airportinfo("airport") -// airportinfo(, [, ]); -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 , 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; rnumRunways(); ++r) { - FGRunway* rwy(apt->getRunwayByIndex(r)); - - naRef rwyid = naStr_fromdata(naNewString(c), - const_cast(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 sids(rwy->getSIDs()); - naRef sidVec = naNewVector(c); - - for (unsigned int s=0; s < sids.size(); ++s) { - naRef procId = naStr_fromdata(naNewString(c), - const_cast(sids[s]->ident().c_str()), - sids[s]->ident().length()); - naVec_append(sidVec, procId); - } - HASHSET("sids", 4, sidVec); - - std::vector stars(rwy->getSTARs()); - naRef starVec = naNewVector(c); - - for (unsigned int s=0; s < stars.size(); ++s) { - naRef procId = naStr_fromdata(naNewString(c), - const_cast(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(id.c_str()), id.length())); - HASHSET("name", 4, naStr_fromdata(naNewString(c), - const_cast(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; -} - - -// Returns vector of data hash for navaid of a , nil on error -// navaids sorted by ascending distance -// navinfo([,],[],[]) -// lat/lon (numeric): use latitude/longitude instead of ac position -// type: ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any") -// id: (partial) id of the fix -// examples: -// navinfo("vor") returns all vors -// navinfo("HAM") return all navaids who's name start with "HAM" -// navinfo("vor", "HAM") return all vor who's name start with "HAM" -//navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM" -// sorted by distance relative to lat=34, lon=48 -static naRef f_navinfo(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; - - 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()); - } - - FGPositioned::Type type = FGPositioned::INVALID; - nav_list_type navlist; - const char * id = ""; - - if(argc > 0 && naIsString(args[0])) { - const char *s = naStr_data(args[0]); - if(!strcmp(s, "any")) type = FGPositioned::INVALID; - else if(!strcmp(s, "fix")) type = FGPositioned::FIX; - else if(!strcmp(s, "vor")) type = FGPositioned::VOR; - else if(!strcmp(s, "ndb")) type = FGPositioned::NDB; - else if(!strcmp(s, "ils")) type = FGPositioned::ILS; - else if(!strcmp(s, "dme")) type = FGPositioned::DME; - else if(!strcmp(s, "tacan")) type = FGPositioned::TACAN; - else id = s; // this is an id - ++args; - --argc; - } - - if(argc > 0 && naIsString(args[0])) { - if( *id != 0 ) { - naRuntimeError(c, "navinfo() called with navaid id"); - return naNil(); - } - id = naStr_data(args[0]); - ++args; - --argc; - } - - if( argc > 0 ) { - naRuntimeError(c, "navinfo() called with too many arguments"); - return naNil(); - } - - navlist = globals->get_navlist()->findByIdentAndFreq( pos, id, 0.0, type ); - - naRef reply = naNewVector(c); - for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) { - const FGNavRecord * nav = *it; - - // set navdata hash - naRef navdata = naNewHash(c); -#define HASHSET(s,l,n) naHash_set(navdata, naStr_fromdata(naNewString(c),s,l),n) - HASHSET("id", 2, naStr_fromdata(naNewString(c), - const_cast(nav->ident().c_str()), nav->ident().length())); - HASHSET("name", 4, naStr_fromdata(naNewString(c), - const_cast(nav->name().c_str()), nav->name().length())); - HASHSET("frequency", 9, naNum(nav->get_freq())); - HASHSET("lat", 3, naNum(nav->get_lat())); - HASHSET("lon", 3, naNum(nav->get_lon())); - HASHSET("elevation", 9, naNum(nav->get_elev_ft() * SG_FEET_TO_METER)); - HASHSET("type", 4, naStr_fromdata(naNewString(c), - const_cast(nav->nameForType(nav->type())), strlen(nav->nameForType(nav->type())))); - HASHSET("distance", 8, naNum(SGGeodesy::distanceNm( pos, nav->geod() ) * SG_NM_TO_METER ) ); - HASHSET("bearing", 7, naNum(SGGeodesy::courseDeg( pos, nav->geod() ) ) ); -#undef HASHSET - naVec_append( reply, navdata ); - } - return reply; -} - // 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 }, @@ -798,13 +766,11 @@ static struct { const char* name; naCFunction func; } funcs[] = { { "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 }, - { "navinfo", f_navinfo }, { 0, 0 } }; @@ -813,8 +779,16 @@ naRef FGNasalSys::cmdArgGhost() 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(); @@ -841,11 +815,26 @@ void FGNasalSys::init() // 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 simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal")); @@ -864,20 +853,102 @@ void FGNasalSys::init() 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::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::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("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::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 @@ -901,10 +972,8 @@ bool pathSortPredicate(const SGPath& p1, const SGPath& p2) 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); - + // 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; i0) + if (! scripts.empty()) { SGPropertyNode* nasal = globals->get_props()->getNode("nasal"); SGPropertyNode* module_node = nasal->getChild(moduleName,0,true); @@ -1004,6 +1073,7 @@ void FGNasalSys::loadPropertyScripts(SGPropertyNode* n) if (enable) { FGNasalModuleListener* listener = new FGNasalModuleListener(n); + _moduleListeners.push_back(listener); enable->addChangeListener(listener, false); } } @@ -1015,12 +1085,14 @@ void FGNasalSys::loadPropertyScripts(SGPropertyNode* n) // 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; igetStringValue("script"); - const char* moduleName = arg->getStringValue("module"); - naRef code = parse(arg->getPath(true).c_str(), 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); } } @@ -1123,10 +1214,22 @@ bool FGNasalSys::handleCommand(const SGPropertyNode* arg) // 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 @@ -1173,14 +1276,12 @@ void FGNasalSys::handleTimer(NasalTimer* t) 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() @@ -1251,8 +1352,43 @@ naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args) 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, @@ -1283,7 +1419,6 @@ FGNasalListener::~FGNasalListener() 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); @@ -1349,62 +1484,6 @@ bool FGNasalListener::changed(SGPropertyNode* node) } } - - -// FGNasalModelData class. If sgLoad3DModel() is called with a pointer to -// such a class, then it lets modelLoaded() run the script, and the -// destructor the 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 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) :