X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FScripting%2FNasalSys.cxx;h=502a406b2e6f88fed49e6bf310c7c7d2b459212f;hb=b5c46a8d59120f18b0bc268af72ecb6d3a75b3e3;hp=7ae4244647223dc679060b61f034747f5cff2ba6;hpb=52306b9093ee2c9d8483d02805dfa5fcf40fb526;p=flightgear.git diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 7ae424464..502a406b2 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -3,10 +3,16 @@ # include "config.h" #endif +#ifdef HAVE_SYS_TIME_H +# include // gettimeofday +#endif + #include #include #include #include +#include +#include #include @@ -15,13 +21,23 @@ #include #include #include +#include #include +#include +#include +#include +#include #include
#include
+#include
+#include #include "NasalSys.hxx" +static FGNasalSys* nasalSys = 0; + + // 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 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen. @@ -52,20 +68,42 @@ static char* readfile(const char* file, int* lenOut) FGNasalSys::FGNasalSys() { + nasalSys = this; _context = 0; _globals = naNil(); _gcHash = naNil(); _nextGCKey = 0; // Any value will do + _callCount = 0; +} + +// Does a naCall() in a new context. Wrapped here to make lock +// tracking easier. Extension functions are called with the lock, but +// we have to release it before making a new naCall(). So rather than +// drop the lock in every extension function that might call back into +// Nasal, we keep a stack depth counter here and only unlock/lock +// around the naCall if it isn't the first one. +naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals) +{ + 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; } FGNasalSys::~FGNasalSys() { - // Nasal doesn't have a "destroy context" API yet. :( - // Not a problem for a global subsystem that will never be - // destroyed. And the context is actually a global, so no memory - // is technically leaked (although the GC pool memory obviously - // won't be freed). - _context = 0; + nasalSys = 0; + map::iterator it, end = _listener.end(); + for(it = _listener.begin(); it != end; ++it) + delete it->second; + + naFreeContext(_context); _globals = naNil(); } @@ -75,12 +113,8 @@ bool FGNasalSys::parseAndRun(const char* sourceCode) strlen(sourceCode)); if(naIsNil(code)) return false; - - naCall(_context, code, 0, 0, naNil(), naNil()); - - if(!naGetError(_context)) return true; - logError(_context); - return false; + call(code, 0, 0, naNil()); + return true; } FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) @@ -91,7 +125,7 @@ FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) char buf[256]; if(!name) { - sprintf(buf, "FGNasalScript@%p", script); + sprintf(buf, "FGNasalScript@%p", (void *)script); name = buf; } @@ -124,11 +158,16 @@ void FGNasalSys::hashset(naRef hash, const char* key, naRef val) static SGPropertyNode* findnode(naContext c, naRef* vec, int len) { SGPropertyNode* p = globals->get_props(); - for(int i=0; igetNode(naStr_data(a)); - if(p == 0) return 0; + try { + for(int i=0; igetNode(naStr_data(a)); + if(p == 0) return 0; + } + } catch (const string& err) { + naRuntimeError(c, (char *)err.c_str()); + return 0; } return p; } @@ -138,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(); } @@ -185,9 +225,19 @@ static naRef f_setprop(naContext c, naRef me, int argc, naRef* args) SGPropertyNode* props = globals->get_props(); naRef val = args[argc-1]; - if(naIsString(val)) props->setStringValue(buf, naStr_data(val)); - else props->setDoubleValue(buf, naNumValue(val).num); - return naNil(); + bool result = false; + try { + 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"); + result = props->setDoubleValue(buf, n.num); + } + } catch (const string& err) { + naRuntimeError(c, (char *)err.c_str()); + } + return naNum(result); #undef BUFLEN } @@ -196,24 +246,15 @@ static naRef f_setprop(naContext c, naRef me, int argc, naRef* args) // make sure it appears. Is there better way to do this? static naRef f_print(naContext c, naRef me, int argc, naRef* args) { -#define BUFLEN 1024 - char buf[BUFLEN + 1]; - buf[BUFLEN] = 0; // extra nul to handle strncpy brain damage - buf[0] = 0; // Zero-length in case there are no arguments - char* p = buf; - int buflen = BUFLEN; + string buf; int n = argc; for(int i=0; i 0 ? args[0] : naNil(); + naRef props = argc > 1 ? args[1] : naNil(); + if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props))) naRuntimeError(c, "bad arguments to fgcommand()"); - naRef cmd = args[0], props = args[1]; - SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(props); - globals->get_commands()->execute(naStr_data(cmd), *node); - return naNil(); - + SGPropertyNode_ptr tmp, *node; + if(!naIsNil(props)) + node = (SGPropertyNode_ptr*)naGhost_ptr(props); + else { + tmp = new SGPropertyNode(); + node = &tmp; + } + return naNum(globals->get_commands()->execute(naStr_data(cmd), *node)); } // settimer(func, dt, simtime) extension function. Falls through to // FGNasalSys::setTimer(). See there for docs. static naRef f_settimer(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - nasal->setTimer(argc, args); + nasalSys->setTimer(c, argc, args); return naNil(); } @@ -243,24 +288,21 @@ static naRef f_settimer(naContext c, naRef me, int argc, naRef* args) // FGNasalSys::setListener(). See there for docs. static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - return nasal->setListener(argc, args); + return nasalSys->setListener(c, argc, args); } // removelistener(int) extension function. Falls through to // FGNasalSys::removeListener(). See there for docs. static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - return nasal->removeListener(argc, args); + return nasalSys->removeListener(c, argc, args); } // Returns a ghost handle to the argument to the currently executing // command static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - return nasal->cmdArgGhost(); + return nasalSys->cmdArgGhost(); } // Sets up a property interpolation. The first argument is either a @@ -285,9 +327,12 @@ static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args) deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num; } - ((SGInterpolator*)globals->get_subsystem("interpolator")) + ((SGInterpolator*)globals->get_subsystem_mgr() + ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator")) ->interpolate(node, nPoints, values, deltas); + delete[] values; + delete[] deltas; return naNil(); } @@ -305,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) { @@ -322,8 +373,249 @@ static naRef f_directory(naContext c, naRef me, int argc, naRef* args) return result; } +// Parse XML file. +// parsexml( [, [, [, [, ]]]]); +// +// ... absolute path to an XML file +// ... callback function with two args: tag name, attribute hash +// ... callback function with one arg: tag name +// ... callback function with one arg: data +// ... 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, or the validated file name otherwise. +static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args) +{ + if(argc < 1 || !naIsString(args[0])) + naRuntimeError(c, "parsexml(): path argument missing or not a string"); + if(argc > 5) argc = 5; + for(int i=1; i(file), strlen(file)); +} + +// Return UNIX epoch time in seconds. +static naRef f_systime(naContext c, naRef me, int argc, naRef* args) +{ +#ifdef WIN32 + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime); + // Converts from 100ns units in 1601 epoch to unix epoch in sec + return naNum((t * 1e-7) - 11644473600.0); +#else + time_t t; + struct timeval td; + do { t = time(0); gettimeofday(&td, 0); } while(t != time(0)); + return naNum(t + 1e-6 * td.tv_usec); +#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())); +#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; +} + + // 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 }, @@ -335,7 +627,14 @@ 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 }, + { "carttogeod", f_carttogeod }, + { "geodtocart", f_geodtocart }, + { "geodinfo", f_geodinfo }, + { "airportinfo", f_airportinfo }, { 0, 0 } }; @@ -354,17 +653,15 @@ void FGNasalSys::init() // sub-reference under the name "globals". This gives client-code // write access to the namespace if someone wants to do something // fancy. - _globals = naStdLib(_context); + _globals = naInit_std(_context); naSave(_context, _globals); hashset(_globals, "globals", _globals); - // Add in the math library under "math" - hashset(_globals, "math", naMathLib(_context)); - - // Add in the IO library. Disabled currently until after the - // 0.9.10 release. - // hashset(_globals, "io", naIOLib(_context)); - // hashset(_globals, "bits", naBitsLib(_context)); + hashset(_globals, "math", naInit_math(_context)); + hashset(_globals, "bits", naInit_bits(_context)); + hashset(_globals, "io", naInit_io(_context)); + hashset(_globals, "thread", naInit_thread(_context)); + hashset(_globals, "utf8", naInit_utf8(_context)); // Add our custom extension functions: for(i=0; funcs[i].name; i++) @@ -394,10 +691,38 @@ void FGNasalSys::init() } 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); + // Pull scripts out of the property tree, too loadPropertyScripts(); } +void FGNasalSys::update(double) +{ + 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 void FGNasalSys::loadPropertyScripts() { @@ -411,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()); @@ -425,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) @@ -481,9 +795,13 @@ void FGNasalSys::loadModule(SGPath file, const char* module) } // Parse and run. Save the local variables namespace, as it will -// become a sub-object of globals. +// 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, - 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)) @@ -498,13 +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; - naCall(_context, code, 0, 0, naNil(), locals); - if(naGetError(_context)) { - logError(_context); - return; - } + call(code, argc, args, locals); hashset(_globals, moduleName, locals); } @@ -512,9 +826,7 @@ void FGNasalSys::deleteModule(const char* moduleName) { naRef modname = naNewString(_context); naStr_fromdata(modname, (char*)moduleName, strlen(moduleName)); - naModLock(); naHash_delete(_globals, modname); - naModUnlock(); } naRef FGNasalSys::parse(const char* filename, const char* buf, int len) @@ -541,29 +853,26 @@ bool FGNasalSys::handleCommand(const SGPropertyNode* arg) naRef code = parse(arg->getPath(true), nasal, strlen(nasal)); if(naIsNil(code)) return false; - naContext c = naNewContext(); + // 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(c); + naRef modname = naNewString(_context); naStr_fromdata(modname, (char*)moduleName, strlen(moduleName)); - if(!naHash_get(_globals, modname, &locals)) - locals = naNewHash(c); + if(!naHash_get(_globals, modname, &locals)) { + locals = naNewHash(_context); + naHash_set(_globals, modname, locals); + } } - // Cache the command argument for inspection via cmdarg(). For + + // Cache this command's argument for inspection via cmdarg(). For // performance reasons, we won't bother with it if the invoked // code doesn't need it. _cmdArg = (SGPropertyNode*)arg; - // Call it! - naModUnlock(); - naRef result = naCall(c, code, 0, 0, naNil(), locals); - naModLock(); - bool error = naGetError(c); - if(error) - logError(c); - - naFreeContext(c); - return !error; + call(code, 0, 0, locals); + return true; } // settimer(func, dt, simtime) extension function. The first argument @@ -576,15 +885,20 @@ bool FGNasalSys::handleCommand(const SGPropertyNode* arg) // "saved" somehow lest they be inadvertently cleaned. In this case, // they are inserted into a globals.__gcsave hash and removed on // expiration. -void FGNasalSys::setTimer(int argc, naRef* args) +void FGNasalSys::setTimer(naContext c, int argc, naRef* args) { // Extract the handler, delta, and simtime arguments: naRef handler = argc > 0 ? args[0] : naNil(); - if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) + if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) { + naRuntimeError(c, "settimer() with invalid function argument"); return; + } naRef delta = argc > 1 ? args[1] : naNil(); - if(naIsNil(delta)) return; + if(naIsNil(delta)) { + naRuntimeError(c, "settimer() with invalid time argument"); + return; + } bool simtime = (argc > 2 && naTrue(args[2])) ? false : true; @@ -601,9 +915,7 @@ void FGNasalSys::setTimer(int argc, naRef* args) void FGNasalSys::handleTimer(NasalTimer* t) { - naCall(_context, t->handler, 0, 0, naNil(), naNil()); - if(naGetError(_context)) - logError(_context); + call(t->handler, 0, 0, naNil()); gcRelease(t->gcKey); } @@ -627,29 +939,43 @@ 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. -naRef FGNasalSys::setListener(int argc, naRef* args) +// 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; 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(); + else { + naRuntimeError(c, "setlistener() with invalid property argument"); + return naNil(); + } - naRef handler = argc > 1 ? args[1] : naNil(); - if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) + if(node->isTied()) + SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " << + node->getPath()); + + 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)); - node->addChangeListener(nl, initial); + node->addChangeListener(nl, init); _listener[_listenerId] = nl; return naNum(_listenerId++); @@ -657,20 +983,19 @@ naRef FGNasalSys::setListener(int argc, naRef* args) // removelistener(int) extension function. The argument is the id of // a listener as returned by the setlistener() function. -naRef FGNasalSys::removeListener(int argc, naRef* args) +naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args) { naRef id = argc > 0 ? args[0] : naNil(); - if(!naIsNum(id)) - return naNil(); + map::iterator it = _listener.find(int(id.num)); - int i = int(id.num); - if (_listener.find(i) == _listener.end()) + if(!naIsNum(id) || it == _listener.end() || it->second->_dead) { + naRuntimeError(c, "removelistener() with invalid listener id"); return naNil(); + } - FGNasalListener *nl = _listener[i]; - nl->_node->removeChangeListener(nl); - _listener.erase(i); - delete nl; + it->second->_dead = true; + _dead_listener.push_back(it->second); + _listener.erase(it); return naNum(_listener.size()); } @@ -678,40 +1003,99 @@ naRef FGNasalSys::removeListener(int argc, naRef* args) // FGNasalListener class. -FGNasalListener::FGNasalListener(SGPropertyNode_ptr node, naRef handler, - FGNasalSys* nasal, int key) : +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), - _active(0) + _init(init), + _type(type), + _active(0), + _dead(false), + _last_int(0L), + _last_float(0.0) { + if(_type == 0 && !_init) + changed(node); } FGNasalListener::~FGNasalListener() { + _node->removeChangeListener(this); _nas->gcRelease(_gcKey); } -void FGNasalListener::valueChanged(SGPropertyNode* node) +void FGNasalListener::call(SGPropertyNode* which, naRef mode) { - if (_active) { - SG_LOG(SG_NASAL, SG_ALERT, "Recursive listener call " - "on property " << node->getPath()); - return; - } + if(_active || _dead) return; + SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id); _active++; - _nas->_cmdArg = node; - naContext c = naNewContext(); - naModUnlock(); - naCall(c, _handler, 0, 0, naNil(), naNil()); - naModLock(); - if(naGetError(c)) - _nas->logError(c); - naFreeContext(c); + 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; + } + } +} @@ -720,41 +1104,107 @@ 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, - ssgBranch *) + 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"); - if (!load && !_unload) + SGPropertyNode *load = nasal->getNode("load"); + _unload = nasal->getNode("unload"); + if(!load && !_unload) return; - _module = path; + std::stringstream m; + m << "__model" << _module_id++; + _module = m.str(); + const char *s = load ? load->getStringValue() : ""; - FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal"); - nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s)); + + 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()) + if(_module.empty()) return; - FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal"); - if (!nas) { - SG_LOG(SG_NASAL, SG_ALERT, "Trying to run an script " + if(!nasalSys) { + SG_LOG(SG_NASAL, SG_WARN, "Trying to run an script " "without Nasal subsystem present."); return; } - if (_unload) { + if(_unload) { const char *s = _unload->getStringValue(); - nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s)); + 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) : + _c(naSubContext(c)), + _start_element(argc > 1 ? args[1] : naNil()), + _end_element(argc > 2 ? args[2] : naNil()), + _data(argc > 3 ? args[3] : naNil()), + _pi(argc > 4 ? args[4] : naNil()) +{ +} + +void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a) +{ + if(naIsNil(_start_element)) return; + naRef attr = naNewHash(_c); + for(int i=0; ideleteModule(_module.c_str()); + call(_start_element, 2, make_string(tag), attr); +} + +void NasalXMLVisitor::endElement(const char* tag) +{ + if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag)); +} + +void NasalXMLVisitor::data(const char* str, int len) +{ + if(!naIsNil(_data)) call(_data, 1, make_string(str, len)); +} + +void NasalXMLVisitor::pi(const char* target, const char* data) +{ + if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data)); +} + +void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b) +{ + naRef args[2]; + args[0] = a; + args[1] = b; + naCall(_c, func, num, args, naNil(), naNil()); + if(naGetError(_c)) + naRethrowError(_c); +} + +naRef NasalXMLVisitor::make_string(const char* s, int n) +{ + return naStr_fromdata(naNewString(_c), const_cast(s), + n < 0 ? strlen(s) : n); }