X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FScripting%2FNasalSys.cxx;h=502a406b2e6f88fed49e6bf310c7c7d2b459212f;hb=b5c46a8d59120f18b0bc268af72ecb6d3a75b3e3;hp=6829e5ce298d61ef1db01e7e984ba3aec4a2b2f1;hpb=3511607527c726ba613fd1b789473519d8ae89b5;p=flightgear.git diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 6829e5ce2..502a406b2 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -23,11 +24,13 @@ #include #include #include +#include #include #include #include
#include
+#include
#include #include "NasalSys.hxx" @@ -71,7 +74,6 @@ FGNasalSys::FGNasalSys() _gcHash = naNil(); _nextGCKey = 0; // Any value will do _callCount = 0; - _purgeListeners = false; } // Does a naCall() in a new context. Wrapped here to make lock @@ -80,12 +82,12 @@ 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, naRef locals) +naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals) { naContext ctx = naNewContext(); if(_callCount) naModUnlock(); _callCount++; - naRef result = naCall(ctx, code, 0, 0, naNil(), locals); + naRef result = naCall(ctx, code, argc, args, naNil(), locals); if(naGetError(ctx)) logError(ctx); _callCount--; @@ -111,7 +113,7 @@ bool FGNasalSys::parseAndRun(const char* sourceCode) strlen(sourceCode)); if(naIsNil(code)) return false; - call(code, naNil()); + call(code, 0, 0, naNil()); return true; } @@ -175,24 +177,25 @@ static SGPropertyNode* findnode(naContext c, naRef* vec, int len) // 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: + case props::BOOL: case props::INT: + case props::LONG: case props::FLOAT: + case props::DOUBLE: return naNum(p->getDoubleValue()); - case SGPropertyNode::STRING: - case SGPropertyNode::UNSPECIFIED: + 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(); } @@ -222,18 +225,19 @@ static naRef f_setprop(naContext c, naRef me, int argc, naRef* args) SGPropertyNode* props = globals->get_props(); naRef val = args[argc-1]; + bool result = false; try { - if(naIsString(val)) 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"); - props->setDoubleValue(buf, n.num); + result = props->setDoubleValue(buf, n.num); } } catch (const string& err) { naRuntimeError(c, (char *)err.c_str()); } - return naNil(); + return naNum(result); #undef BUFLEN } @@ -327,6 +331,8 @@ static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args) ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator")) ->interpolate(node, nPoints, values, deltas); + delete[] values; + delete[] deltas; return naNil(); } @@ -344,6 +350,12 @@ static naRef f_srand(naContext c, naRef me, int argc, naRef* args) return naNum(0); } +static naRef f_abort(naContext c, naRef me, int argc, naRef* args) +{ + abort(); + return naNil(); +} + // Return an array listing of all files in a directory static naRef f_directory(naContext c, naRef me, int argc, naRef* args) { @@ -371,7 +383,7 @@ static naRef f_directory(naContext c, naRef me, int argc, naRef* args) // ... callback function with two args: target, data // (pi = "processing instruction") // All four callback functions are optional and default to nil. -// The function returns nil on error, and the file name otherwise. +// The function returns nil on error, or the validated file name otherwise. static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args) { if(argc < 1 || !naIsString(args[0])) @@ -381,18 +393,22 @@ static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args) if(!(naIsNil(args[i]) || naIsFunc(args[i]))) naRuntimeError(c, "parsexml(): callback argument not a function"); - const char* file = naStr_data(args[0]); + const char* file = fgValidatePath(naStr_data(args[0]), false); + if(!file) { + naRuntimeError(c, "parsexml(): reading '%s' denied " + "(unauthorized access)", naStr_data(args[0])); + return naNil(); + } std::ifstream input(file); NasalXMLVisitor visitor(c, argc, args); try { readXML(input, visitor); } catch (const sg_exception& e) { - string msg = string("parsexml(): file '") + file + "' " - + e.getFormattedMessage(); - naRuntimeError(c, msg.c_str()); + naRuntimeError(c, "parsexml(): file '%s' %s", + file, e.getFormattedMessage().c_str()); return naNil(); } - return args[0]; + return naStr_fromdata(naNewString(c), const_cast(file), strlen(file)); } // Return UNIX epoch time in seconds. @@ -410,7 +426,6 @@ static naRef f_systime(naContext c, naRef me, int argc, naRef* args) do { t = time(0); gettimeofday(&td, 0); } while(t != time(0)); return naNum(t + 1e-6 * td.tv_usec); #endif - } // Convert a cartesian point to a geodetic lat/lon/altitude. @@ -453,12 +468,14 @@ static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args) 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"); + 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; + double elev = argc == 3 ? naNumValue(args[2]).num : 10000; const SGMaterial *mat; - if(!globals->get_scenery()->get_elevation_m(lat, lon, 10000.0, elev, &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)); @@ -483,63 +500,101 @@ static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args) #undef HASHSET } -// Returns airport data for given airport id ("KSFO"), or for the airport -// nearest to a given lat/lon pair, or without arguments, to the current -// aircraft position. Returns nil on error. Only one side of each runway is -// returned. + +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 lat = fgGetNode("/position/latitude-deg", true); - static SGConstPropertyNode_ptr lon = fgGetNode("/position/longitude-deg", true); - - // airport - FGAirportList *aptlst = globals->get_airports(); - FGAirport *apt; - if(argc == 0) - apt = aptlst->search(lon->getDoubleValue(), lat->getDoubleValue(), false); - else if(argc == 1 && naIsString(args[0])) - apt = aptlst->search(naStr_data(args[0])); - else if(argc == 2 && naIsNum(args[0]) && naIsNum(args[1])) - apt = aptlst->search(args[1].num, args[0].num, false); - else { + 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) { - naRuntimeError(c, "airportinfo(): no airport found"); - return naNil(); + apt = FGAirport::findClosest(pos, maxRange, &filter); + if(!apt) return naNil(); } - string id = apt->getId(); - string name = apt->getName(); + string id = apt->ident(); + string name = apt->name(); // set runway hash - FGRunwayList *rwylst = globals->get_runways(); - FGRunway rwy; naRef rwys = naNewHash(c); - if(rwylst->search(id, &rwy)) { - do { - if(rwy._id != id) continue; - if(rwy._type != "runway") continue; + 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); + naRef rwydata = naNewHash(c); #define HASHSET(s,l,n) naHash_set(rwydata, naStr_fromdata(naNewString(c),s,l),n) - HASHSET("lat", 3, naNum(rwy._lat)); - HASHSET("lon", 3, naNum(rwy._lon)); - HASHSET("heading", 7, naNum(rwy._heading)); - HASHSET("length", 6, naNum(rwy._length * SG_FEET_TO_METER)); - HASHSET("width", 5, naNum(rwy._width * SG_FEET_TO_METER)); - HASHSET("threshold1", 10, naNum(rwy._displ_thresh1 * SG_FEET_TO_METER)); - HASHSET("threshold2", 10, naNum(rwy._displ_thresh2 * SG_FEET_TO_METER)); - HASHSET("stopway1", 8, naNum(rwy._stopway1 * SG_FEET_TO_METER)); - HASHSET("stopway2", 8, naNum(rwy._stopway2 * SG_FEET_TO_METER)); + 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 - - naRef no = naStr_fromdata(naNewString(c), - const_cast(rwy._rwy_no.c_str()), - rwy._rwy_no.length()); - naHash_set(rwys, no, rwydata); - } while(rwylst->next(&rwy)); + naHash_set(rwys, rwyid, rwydata); } // set airport hash @@ -560,7 +615,7 @@ static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args) // Table of extension functions. Terminate with zeros. -static struct { char* name; naCFunction func; } funcs[] = { +static struct { const char* name; naCFunction func; } funcs[] = { { "getprop", f_getprop }, { "setprop", f_setprop }, { "print", f_print }, @@ -572,6 +627,7 @@ static struct { char* name; naCFunction func; } funcs[] = { { "_interpolate", f_interpolate }, { "rand", f_rand }, { "srand", f_srand }, + { "abort", f_abort }, { "directory", f_directory }, { "parsexml", f_parsexml }, { "systime", f_systime }, @@ -639,7 +695,7 @@ void FGNasalSys::init() 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(); @@ -647,19 +703,24 @@ void FGNasalSys::init() void FGNasalSys::update(double) { - if(_purgeListeners) { - _purgeListeners = false; - map::iterator it; - for(it = _listener.begin(); it != _listener.end();) { - FGNasalListener *nl = it->second; - if(nl->_dead) { - _listener.erase(it++); - delete nl; - } else { - ++it; - } - } + if(!_dead_listener.empty()) { + vector::iterator it, end = _dead_listener.end(); + for(it = _dead_listener.begin(); it != end; ++it) delete *it; + _dead_listener.clear(); } + + // 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 + // naNew*() calls, which end up leaking memory because the context + // only clears out its temporary vector when it's *used*. So just + // junk it and fetch a new/reinitialized one every frame. This is + // clumsy: the right solution would use the dynamic context in all + // cases and eliminate _context entirely. But that's more work, + // and this works fine (yes, they say "New" and "Free", but + // they're very fast, just trust me). -Andy + naFreeContext(_context); + _context = naNewContext(); } // Loads the scripts found under /nasal in the global tree @@ -675,12 +736,12 @@ void FGNasalSys::loadPropertyScripts() if(n->hasChild("module")) module = n->getStringValue("module"); - // allow multiple files to be specified within in a single + // allow multiple files to be specified within a single // Nasal module tag int j = 0; SGPropertyNode *fn; bool file_specified = false; - while ( (fn = n->getChild("file", j)) != NULL ) { + while((fn = n->getChild("file", j)) != NULL) { file_specified = true; const char* file = fn->getStringValue(); SGPath p(globals->get_fg_root()); @@ -689,17 +750,6 @@ void FGNasalSys::loadPropertyScripts() 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) @@ -749,7 +799,9 @@ void FGNasalSys::loadModule(SGPath file, const char* module) // 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, - const char* src, int len, const SGPropertyNode* arg) + const char* src, int len, + const SGPropertyNode* cmdarg, + int argc, naRef* args) { naRef code = parse(fileName, src, len); if(naIsNil(code)) @@ -764,9 +816,9 @@ void FGNasalSys::createModule(const char* moduleName, const char* fileName, if(!naHash_get(_globals, modname, &locals)) locals = naNewHash(_context); - _cmdArg = (SGPropertyNode*)arg; + _cmdArg = (SGPropertyNode*)cmdarg; - call(code, locals); + call(code, argc, args, locals); hashset(_globals, moduleName, locals); } @@ -819,7 +871,7 @@ bool FGNasalSys::handleCommand(const SGPropertyNode* arg) // code doesn't need it. _cmdArg = (SGPropertyNode*)arg; - call(code, locals); + call(code, 0, 0, locals); return true; } @@ -863,7 +915,7 @@ void FGNasalSys::setTimer(naContext c, int argc, naRef* args) void FGNasalSys::handleTimer(NasalTimer* t) { - call(t->handler, naNil()); + call(t->handler, 0, 0, naNil()); gcRelease(t->gcKey); } @@ -887,12 +939,16 @@ void FGNasalSys::NasalTimer::timerExpired() int FGNasalSys::_listenerId = 0; -// setlistener(property, func, bool) extension function. The first argument -// is either a ghost (SGPropertyNode_ptr*) or a string (global property -// path), the second is a Nasal function, the optional third one a bool. -// If the bool is true, then the listener is executed initially. The -// setlistener() function returns a unique id number, that can be used -// as argument to the removelistener() function. +// setlistener(, [, [, ]]) +// Attaches a callback function to a property (specified as a global +// property path string or a SGPropertyNode_ptr* ghost). If the third, +// optional argument (default=0) is set to 1, then the function is also +// called initially. If the fourth, optional argument is set to 0, then the +// function is only called when the property node value actually changes. +// Otherwise it's called independent of the value whenever the node is +// written to (default). The setlistener() function returns a unique +// id number, which is to be used as argument to the removelistener() +// function. naRef FGNasalSys::setListener(naContext c, int argc, naRef* args) { SGPropertyNode_ptr node; @@ -908,17 +964,18 @@ naRef FGNasalSys::setListener(naContext c, int argc, naRef* args) SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " << node->getPath()); - naRef handler = argc > 1 ? args[1] : naNil(); - if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) { + naRef code = argc > 1 ? args[1] : naNil(); + if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) { naRuntimeError(c, "setlistener() with invalid function argument"); return naNil(); } - bool initial = argc > 2 && naTrue(args[2]); + 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, init, type); - FGNasalListener *nl = new FGNasalListener(node, handler, this, - gcSave(handler), _listenerId); - node->addChangeListener(nl, initial); + node->addChangeListener(nl, init); _listener[_listenerId] = nl; return naNum(_listenerId++); @@ -936,15 +993,9 @@ naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args) return naNil(); } - FGNasalListener *nl = it->second; - if(nl->_active) { - nl->_dead = true; - _purgeListeners = true; - return naNum(-1); - } - + it->second->_dead = true; + _dead_listener.push_back(it->second); _listener.erase(it); - delete nl; return naNum(_listener.size()); } @@ -952,16 +1003,23 @@ naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args) // FGNasalListener class. -FGNasalListener::FGNasalListener(SGPropertyNode_ptr node, naRef handler, - FGNasalSys* nasal, int key, int id) : +FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code, + FGNasalSys* nasal, int key, int id, + int init, int type) : _node(node), - _handler(handler), + _code(code), _gcKey(key), _id(id), _nas(nasal), + _init(init), + _type(type), _active(0), - _dead(false) + _dead(false), + _last_int(0L), + _last_float(0.0) { + if(_type == 0 && !_init) + changed(node); } FGNasalListener::~FGNasalListener() @@ -970,19 +1028,74 @@ FGNasalListener::~FGNasalListener() _nas->gcRelease(_gcKey); } -void FGNasalListener::valueChanged(SGPropertyNode* node) +void FGNasalListener::call(SGPropertyNode* which, naRef mode) { - // drop recursive listener calls - if(_active || _dead) - return; - + if(_active || _dead) return; SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id); _active++; - _nas->_cmdArg = node; - _nas->call(_handler, naNil()); + naRef arg[4]; + arg[0] = _nas->propNodeGhost(which); + arg[1] = _nas->propNodeGhost(_node); + arg[2] = mode; // value changed, child added/removed + arg[3] = naNum(_node != which); // child event? + _nas->call(_code, 4, arg, naNil()); _active--; } +void FGNasalListener::valueChanged(SGPropertyNode* node) +{ + if(_type < 2 && node != _node) return; // skip child events + if(_type > 0 || changed(_node) || _init) + call(node, naNum(0)); + + _init = 0; +} + +void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child) +{ + if(_type == 2) call(child, naNum(1)); +} + +void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child) +{ + if(_type == 2) call(child, naNum(-1)); +} + +bool FGNasalListener::changed(SGPropertyNode* node) +{ + 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 props::BOOL: + case props::INT: + case props::LONG: + { + long l = node->getLongValue(); + result = l != _last_int; + _last_int = l; + return result; + } + case props::FLOAT: + case props::DOUBLE: + { + double d = node->getDoubleValue(); + result = d != _last_float; + _last_float = d; + return result; + } + default: + { + string s = node->getStringValue(); + result = s != _last_string; + _last_string = s; + return result; + } + } +} @@ -991,23 +1104,33 @@ void FGNasalListener::valueChanged(SGPropertyNode* node) // 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 *) { - SGPropertyNode *n = prop->getNode("nasal"), *load; - if(!n) + if(!prop) + return; + SGPropertyNode *nasal = prop->getNode("nasal"); + if(!nasal) return; - load = n->getNode("load"); - _unload = n->getNode("unload"); + SGPropertyNode *load = nasal->getNode("load"); + _unload = nasal->getNode("unload"); if(!load && !_unload) return; - _module = path; - if(_props) - _module += ':' + _props->getPath(); + std::stringstream m; + m << "__model" << _module_id++; + _module = m.str(); + const char *s = load ? load->getStringValue() : ""; - nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props); + + 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() @@ -1023,7 +1146,7 @@ FGNasalModelData::~FGNasalModelData() if(_unload) { const char *s = _unload->getStringValue(); - nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props); + nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root); } nasalSys->deleteModule(_module.c_str()); }