X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FScripting%2FNasalSys.cxx;h=991147e790c5619d3b4e0ad14e24f86fe1f47612;hb=50e19ee23970db9bf7314d74f0ed223d8bf702c0;hp=80adc19eaa63dac153fbd020f9ad6260d0096d72;hpb=79e48829da50e0103e81684896b8287bfe23cc96;p=flightgear.git diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 80adc19ea..991147e79 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -1,19 +1,38 @@ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_SYS_TIME_H +# include // gettimeofday +#endif + #include #include #include #include +#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. @@ -44,20 +63,43 @@ static char* readfile(const char* file, int* lenOut) FGNasalSys::FGNasalSys() { + nasalSys = this; _context = 0; _globals = naNil(); - _timerHash = naNil(); - _nextTimerHashKey = 0; // Any value will do + _gcHash = naNil(); + _nextGCKey = 0; // Any value will do + _callCount = 0; + _purgeListeners = false; +} + +// 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, naRef locals) +{ + naContext ctx = naNewContext(); + if(_callCount) naModUnlock(); + _callCount++; + naRef result = naCall(ctx, code, 0, 0, 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(); } @@ -67,12 +109,30 @@ bool FGNasalSys::parseAndRun(const char* sourceCode) strlen(sourceCode)); if(naIsNil(code)) return false; + call(code, naNil()); + return true; +} - naCall(_context, code, naNil(), naNil(), naNil()); +FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) +{ + FGNasalScript* script = new FGNasalScript(); + script->_gcKey = -1; // important, if we delete it on a parse + script->_nas = this; // error, don't clobber a real handle! + + char buf[256]; + if(!name) { + sprintf(buf, "FGNasalScript@%p", (void *)script); + name = buf; + } - if(!naGetError(_context)) return true; - logError(); - return false; + script->_code = parse(name, src, strlen(src)); + if(naIsNil(script->_code)) { + delete script; + return 0; + } + + script->_gcKey = gcSave(script->_code); + return script; } // Utility. Sets a named key in a hash by C string, rather than nasal @@ -91,14 +151,19 @@ void FGNasalSys::hashset(naRef hash, const char* key, naRef val) // 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) { 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; } @@ -106,9 +171,9 @@ static SGPropertyNode* findnode(naContext c, naRef vec, int len) // getprop() extension function. Concatenates its string arguments as // property names and returns the value of the specified property. Or // nil if it doesn't exist. -static naRef f_getprop(naContext c, naRef args) +static naRef f_getprop(naContext c, naRef me, int argc, naRef* args) { - const SGPropertyNode* p = findnode(c, args, naVec_size(args)); + const SGPropertyNode* p = findnode(c, args, argc); if(!p) return naNil(); switch(p->getType()) { @@ -118,12 +183,14 @@ static naRef f_getprop(naContext c, naRef args) return naNum(p->getDoubleValue()); case SGPropertyNode::STRING: + case SGPropertyNode::UNSPECIFIED: { naRef nastr = naNewString(c); const char* val = p->getStringValue(); naStr_fromdata(nastr, (char*)val, strlen(val)); return nastr; } + case SGPropertyNode::ALIAS: // <--- FIXME, recurse? default: return naNil(); } @@ -132,16 +199,15 @@ static naRef f_getprop(naContext c, naRef args) // setprop() extension function. Concatenates its string arguments as // property names and sets the value of the specified property to the // final argument. -static naRef f_setprop(naContext c, naRef args) +static naRef f_setprop(naContext c, naRef me, int argc, naRef* args) { #define BUFLEN 1024 - int argc = naVec_size(args); char buf[BUFLEN + 1]; buf[BUFLEN] = 0; char* p = buf; int buflen = BUFLEN; for(int i=0; iget_props(); - naRef val = naVec_get(args, argc-1); - if(naIsString(val)) props->setStringValue(buf, naStr_data(val)); - else props->setDoubleValue(buf, naNumValue(val).num); + naRef val = args[argc-1]; + try { + if(naIsString(val)) 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); + } + } catch (const string& err) { + naRuntimeError(c, (char *)err.c_str()); + } return naNil(); #undef BUFLEN } @@ -163,56 +238,247 @@ static naRef f_setprop(naContext c, naRef args) // print() extension function. Concatenates and prints its arguments // to the FlightGear log. Uses the highest log level (SG_ALERT), to // make sure it appears. Is there better way to do this? -static naRef f_print(naContext c, naRef args) +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 - char* p = buf; - int buflen = BUFLEN; - int n = naVec_size(args); + string buf; + int n = argc; for(int i=0; iget_commands()->execute(naStr_data(cmd), *node); - return naNil(); - + naRef cmd = argc > 0 ? args[0] : naNil(); + naRef props = argc > 1 ? args[1] : naNil(); + if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props))) + naRuntimeError(c, "bad arguments to fgcommand()"); + 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 args) +static naRef f_settimer(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - nasal->setTimer(args); + nasalSys->setTimer(c, argc, args); return naNil(); } +// 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) +{ + 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) +{ + 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 args) +static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args) +{ + return nasalSys->cmdArgGhost(); +} + +// Sets up a property interpolation. The first argument is either a +// ghost (SGPropertyNode_ptr*) or a string (global property path) to +// interpolate. The second argument is a vector of pairs of +// 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; iget_subsystem_mgr() + ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator")) + ->interpolate(node, nPoints, values, deltas); + + return naNil(); +} + +// This is a better RNG than the one in the default Nasal distribution +// (which is based on the C library rand() implementation). It will +// override. +static naRef f_rand(naContext c, naRef me, int argc, naRef* args) { - FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal"); - return nasal->cmdArgGhost(); + return naNum(sg_random()); +} + +static naRef f_srand(naContext c, naRef me, int argc, naRef* args) +{ + sg_srandom_time(); + return naNum(0); +} + +// Return an array listing of all files in a directory +static naRef f_directory(naContext c, naRef me, int argc, naRef* args) +{ + 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(); + 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); + return result; +} + +// Parse XML file. +// parsexml( [, [, [, [, ]]]]); +// +// ... absolute path of 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, and the 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; iget_scenery()->get_elevation_m(lat, lon, 10000.0, elev, &mat)) + return naNil(); + naRef vec = naNewVector(c); + naVec_append(vec, naNum(elev)); + naRef matdata = naNil(); + if(mat) { + matdata = naNewHash(c); + naRef names = naNewVector(c); + const vector 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 } // Table of extension functions. Terminate with zeros. @@ -222,7 +488,18 @@ static struct { char* name; naCFunction func; } funcs[] = { { "print", f_print }, { "_fgcommand", f_fgcommand }, { "settimer", f_settimer }, + { "_setlistener", f_setlistener }, + { "removelistener", f_removelistener }, { "_cmdarg", f_cmdarg }, + { "_interpolate", f_interpolate }, + { "rand", f_rand }, + { "srand", f_srand }, + { "directory", f_directory }, + { "parsexml", f_parsexml }, + { "systime", f_systime }, + { "carttogeod", f_carttogeod }, + { "geodtocart", f_geodtocart }, + { "geodinfo", f_geodinfo }, { 0, 0 } }; @@ -241,12 +518,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)); + 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++) @@ -256,10 +536,11 @@ void FGNasalSys::init() // And our SGPropertyNode wrapper hashset(_globals, "props", genPropsModule()); - // Make a "__timers" hash to hold the settimer() handlers (to - // protect them from begin garbage-collected). - _timerHash = naNewHash(_context); - hashset(_globals, "__timers", _timerHash); + // 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); // Now load the various source files in the Nasal directory SGPath p(globals->get_fg_root()); @@ -271,13 +552,37 @@ void FGNasalSys::init() fullpath.append(dent->d_name); SGPath file(dent->d_name); if(file.extension() != "nas") continue; - readScriptFile(fullpath, file.base().c_str()); + loadModule(fullpath, file.base().c_str()); } + 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); // Pull scripts out of the property tree, too loadPropertyScripts(); } +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; + } + } + } +} + // Loads the scripts found under /nasal in the global tree void FGNasalSys::loadPropertyScripts() { @@ -291,20 +596,37 @@ void FGNasalSys::loadPropertyScripts() if(n->hasChild("module")) module = n->getStringValue("module"); + // allow multiple files to be specified within in a single + // Nasal module tag + int j = 0; + SGPropertyNode *fn; + bool file_specified = false; + 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); + 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); - readScriptFile(p, module); + loadModule(p, module); } - + */ + const char* src = n->getStringValue("script"); if(!n->hasChild("script")) src = 0; // Hrm... if(src) - initModule(module, n->getPath(), src, strlen(src)); + createModule(module, n->getPath(), src, strlen(src)); - if(!file && !src) + if(!file_specified && !src) SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " << "no or