X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FScripting%2FNasalSys.cxx;h=991147e790c5619d3b4e0ad14e24f86fe1f47612;hb=50e19ee23970db9bf7314d74f0ed223d8bf702c0;hp=a1475ac008aea98d9a6600d423c2dbb42327017f;hpb=0f704b0a6e524576b536f9a471ab5576c0373f4b;p=flightgear.git diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index a1475ac00..991147e79 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -1,7 +1,17 @@ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_SYS_TIME_H +# include // gettimeofday +#endif + #include #include #include #include +#include #include @@ -10,13 +20,19 @@ #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. @@ -47,20 +63,43 @@ 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; + _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(); } @@ -70,12 +109,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(); - return false; + call(code, naNil()); + return true; } FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) @@ -86,7 +121,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; } @@ -119,11 +154,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; } @@ -180,8 +220,17 @@ 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); + 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 } @@ -191,24 +240,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(); } +// 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 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 @@ -264,7 +321,8 @@ 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); return naNil(); @@ -278,6 +336,151 @@ static naRef f_rand(naContext c, naRef me, int argc, naRef* args) 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. static struct { char* name; naCFunction func; } funcs[] = { { "getprop", f_getprop }, @@ -285,9 +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 } }; @@ -306,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++) @@ -339,11 +554,35 @@ void FGNasalSys::init() if(file.extension() != "nas") continue; 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() { @@ -381,7 +620,7 @@ void FGNasalSys::loadPropertyScripts() loadModule(p, module); } */ - + const char* src = n->getStringValue("script"); if(!n->hasChild("script")) src = 0; // Hrm... if(src) @@ -395,17 +634,17 @@ void FGNasalSys::loadPropertyScripts() } // Logs a runtime error, with stack trace, to the FlightGear log stream -void FGNasalSys::logError() +void FGNasalSys::logError(naContext context) { SG_LOG(SG_NASAL, SG_ALERT, - "Nasal runtime error: " << naGetError(_context)); + "Nasal runtime error: " << naGetError(context)); SG_LOG(SG_NASAL, SG_ALERT, - " at " << naStr_data(naGetSourceFile(_context, 0)) << - ", line " << naGetLine(_context, 0)); - for(int i=1; igetPath(true), nasal, strlen(nasal)); if(naIsNil(code)) 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]) { + if(moduleName[0]) { naRef modname = naNewString(_context); naStr_fromdata(modname, (char*)moduleName, strlen(moduleName)); - if(!naHash_get(_globals, modname, &locals)) + 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! - naRef result = naCall(_context, code, 0, 0, naNil(), locals); - if(!naGetError(_context)) return true; - logError(); - return false; + call(code, locals); + return true; } // 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 -// that "simulator" time (rather than real time) is to be used. +// that "real world" time (rather than simulator time) is to be used. // // Implementation note: the FGTimer objects don't live inside the // garbage collector, so the Nasal handler functions have to be // "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; - - bool simtime = (argc > 2 && naTrue(args[2])) ? true : false; + if(naIsNil(delta)) { + naRuntimeError(c, "settimer() with invalid time argument"); + return; + } + + bool simtime = (argc > 2 && naTrue(args[2])) ? false : true; // Generate and register a C++ timer handler NasalTimer* t = new NasalTimer; @@ -530,9 +784,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(); + call(t->handler, naNil()); gcRelease(t->gcKey); } @@ -553,3 +805,202 @@ void FGNasalSys::NasalTimer::timerExpired() nasal->handleTimer(this); delete this; } + +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(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 { + naRuntimeError(c, "setlistener() with invalid property argument"); + return naNil(); + } + + if(node->isTied()) + 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))) { + naRuntimeError(c, "setlistener() with invalid function argument"); + return naNil(); + } + + bool initial = argc > 2 && naTrue(args[2]); + + FGNasalListener *nl = new FGNasalListener(node, handler, this, + gcSave(handler), _listenerId); + node->addChangeListener(nl, initial); + + _listener[_listenerId] = nl; + return naNum(_listenerId++); +} + +// removelistener(int) extension function. The argument is the id of +// a listener as returned by the setlistener() function. +naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args) +{ + naRef id = argc > 0 ? args[0] : naNil(); + map::iterator it = _listener.find(int(id.num)); + + if(!naIsNum(id) || it == _listener.end() || it->second->_dead) { + naRuntimeError(c, "removelistener() with invalid listener id"); + return naNil(); + } + + FGNasalListener *nl = it->second; + if(nl->_active) { + nl->_dead = true; + _purgeListeners = true; + return naNum(-1); + } + + _listener.erase(it); + delete nl; + return naNum(_listener.size()); +} + + + +// FGNasalListener class. + +FGNasalListener::FGNasalListener(SGPropertyNode_ptr node, naRef handler, + FGNasalSys* nasal, int key, int id) : + _node(node), + _handler(handler), + _gcKey(key), + _id(id), + _nas(nasal), + _active(0), + _dead(false) +{ +} + +FGNasalListener::~FGNasalListener() +{ + _node->removeChangeListener(this); + _nas->gcRelease(_gcKey); +} + +void FGNasalListener::valueChanged(SGPropertyNode* node) +{ + // drop recursive listener calls + if(_active || _dead) + return; + + SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id); + _active++; + _nas->_cmdArg = node; + _nas->call(_handler, naNil()); + _active--; +} + + + + +// 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. + +void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop, + osg::Node *) +{ + SGPropertyNode *n = prop->getNode("nasal"), *load; + if(!n) + return; + + load = n->getNode("load"); + _unload = n->getNode("unload"); + if(!load && !_unload) + return; + + _module = path; + const char *s = load ? load->getStringValue() : ""; + nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props); +} + +FGNasalModelData::~FGNasalModelData() +{ + if(_module.empty()) + return; + + if(!nasalSys) { + SG_LOG(SG_NASAL, SG_ALERT, "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), _props); + } + 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; i(s), + n < 0 ? strlen(s) : n); +} + +