]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
1c3ec31cb2265a5e8f437d3f828ba697f23abad1
[flightgear.git] / src / Scripting / NasalSys.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include "config.h"
4 #endif
5
6 #ifdef HAVE_SYS_TIME_H
7 #  include <sys/time.h>  // gettimeofday
8 #endif
9
10 #include <string.h>
11 #include <stdio.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fstream>
15 #include <sstream>
16 #include <algorithm> // for std::sort
17
18 #include <simgear/nasal/nasal.h>
19 #include <simgear/props/props.hxx>
20 #include <simgear/math/sg_random.h>
21 #include <simgear/misc/sg_path.hxx>
22 #include <simgear/misc/sg_dir.hxx>
23 #include <simgear/misc/interpolator.hxx>
24 #include <simgear/structure/commands.hxx>
25 #include <simgear/math/sg_geodesy.hxx>
26 #include <simgear/structure/event_mgr.hxx>
27
28 #include "NasalSys.hxx"
29 #include "NasalPositioned.hxx"
30 #include <Main/globals.hxx>
31 #include <Main/util.hxx>
32 #include <Main/fg_props.hxx>
33
34 using std::map;
35
36 static FGNasalSys* nasalSys = 0;
37
38 // Listener class for loading Nasal modules on demand
39 class FGNasalModuleListener : public SGPropertyChangeListener
40 {
41 public:
42     FGNasalModuleListener(SGPropertyNode* node);
43
44     virtual void valueChanged(SGPropertyNode* node);
45
46 private:
47     SGPropertyNode_ptr _node;
48 };
49
50 FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node)
51 {
52 }
53
54 void FGNasalModuleListener::valueChanged(SGPropertyNode*)
55 {
56     if (_node->getBoolValue("enabled",false)&&
57         !_node->getBoolValue("loaded",true))
58     {
59         nasalSys->loadPropertyScripts(_node);
60     }
61 }
62
63
64 // Read and return file contents in a single buffer.  Note use of
65 // stat() to get the file size.  This is a win32 function, believe it
66 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
67 // Text mode brain damage will kill us if we're trying to do bytewise
68 // I/O.
69 static char* readfile(const char* file, int* lenOut)
70 {
71     struct stat data;
72     if(stat(file, &data) != 0) return 0;
73     FILE* f = fopen(file, "rb");
74     if(!f) return 0;
75     char* buf = new char[data.st_size];
76     *lenOut = fread(buf, 1, data.st_size, f);
77     fclose(f);
78     if(*lenOut != data.st_size) {
79         // Shouldn't happen, but warn anyway since it represents a
80         // platform bug and not a typical runtime error (missing file,
81         // etc...)
82         SG_LOG(SG_NASAL, SG_ALERT,
83                "ERROR in Nasal initialization: " <<
84                "short count returned from fread() of " << file <<
85                ".  Check your C library!");
86         delete[] buf;
87         return 0;
88     }
89     return buf;
90 }
91
92 FGNasalSys::FGNasalSys()
93 {
94     nasalSys = this;
95     _context = 0;
96     _globals = naNil();
97     _gcHash = naNil();
98     _nextGCKey = 0; // Any value will do
99     _callCount = 0;
100 }
101
102 naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
103 {
104   return callMethod(code, naNil(), argc, args, locals);
105 }
106
107 // Does a naCall() in a new context.  Wrapped here to make lock
108 // tracking easier.  Extension functions are called with the lock, but
109 // we have to release it before making a new naCall().  So rather than
110 // drop the lock in every extension function that might call back into
111 // Nasal, we keep a stack depth counter here and only unlock/lock
112 // around the naCall if it isn't the first one.
113
114 naRef FGNasalSys::callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals)
115 {
116     naContext ctx = naNewContext();
117     if(_callCount) naModUnlock();
118     _callCount++;
119     naRef result = naCall(ctx, code, argc, args, self, locals);
120     if(naGetError(ctx))
121         logError(ctx);
122     _callCount--;
123     if(_callCount) naModLock();
124     naFreeContext(ctx);
125     return result;
126 }
127
128 FGNasalSys::~FGNasalSys()
129 {
130     nasalSys = 0;
131     map<int, FGNasalListener *>::iterator it, end = _listener.end();
132     for(it = _listener.begin(); it != end; ++it)
133         delete it->second;
134
135     naFreeContext(_context);
136     _globals = naNil();
137 }
138
139 bool FGNasalSys::parseAndRun(const char* sourceCode)
140 {
141     naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
142                        strlen(sourceCode));
143     if(naIsNil(code))
144         return false;
145     call(code, 0, 0, naNil());
146     return true;
147 }
148
149 FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
150 {
151     FGNasalScript* script = new FGNasalScript();
152     script->_gcKey = -1; // important, if we delete it on a parse
153     script->_nas = this; // error, don't clobber a real handle!
154
155     char buf[256];
156     if(!name) {
157         sprintf(buf, "FGNasalScript@%p", (void *)script);
158         name = buf;
159     }
160
161     script->_code = parse(name, src, strlen(src));
162     if(naIsNil(script->_code)) {
163         delete script;
164         return 0;
165     }
166
167     script->_gcKey = gcSave(script->_code);
168     return script;
169 }
170
171 // Utility.  Sets a named key in a hash by C string, rather than nasal
172 // string object.
173 void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
174 {
175     naRef s = naNewString(_context);
176     naStr_fromdata(s, (char*)key, strlen(key));
177     naHash_set(hash, s, val);
178 }
179
180 // The get/setprop functions accept a *list* of strings and walk
181 // through the property tree with them to find the appropriate node.
182 // This allows a Nasal object to hold onto a property path and use it
183 // like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
184 // is the utility function that walks the property tree.
185 // Future enhancement: support integer arguments to specify array
186 // elements.
187 static SGPropertyNode* findnode(naContext c, naRef* vec, int len)
188 {
189     SGPropertyNode* p = globals->get_props();
190     try {
191         for(int i=0; i<len; i++) {
192             naRef a = vec[i];
193             if(!naIsString(a)) return 0;
194             p = p->getNode(naStr_data(a));
195             if(p == 0) return 0;
196         }
197     } catch (const string& err) {
198         naRuntimeError(c, (char *)err.c_str());
199         return 0;
200     }
201     return p;
202 }
203
204 // getprop() extension function.  Concatenates its string arguments as
205 // property names and returns the value of the specified property.  Or
206 // nil if it doesn't exist.
207 static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
208 {
209     using namespace simgear;
210     const SGPropertyNode* p = findnode(c, args, argc);
211     if(!p) return naNil();
212
213     switch(p->getType()) {
214     case props::BOOL:   case props::INT:
215     case props::LONG:   case props::FLOAT:
216     case props::DOUBLE:
217         {
218         double dv = p->getDoubleValue();
219         if (osg::isNaN(dv)) {
220           SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
221           return naNil();
222         }
223         
224         return naNum(dv);
225         }
226         
227     case props::STRING:
228     case props::UNSPECIFIED:
229         {
230             naRef nastr = naNewString(c);
231             const char* val = p->getStringValue();
232             naStr_fromdata(nastr, (char*)val, strlen(val));
233             return nastr;
234         }
235     case props::ALIAS: // <--- FIXME, recurse?
236     default:
237         return naNil();
238     }
239 }
240
241 // setprop() extension function.  Concatenates its string arguments as
242 // property names and sets the value of the specified property to the
243 // final argument.
244 static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
245 {
246 #define BUFLEN 1024
247     char buf[BUFLEN + 1];
248     buf[BUFLEN] = 0;
249     char* p = buf;
250     int buflen = BUFLEN;
251     if(argc < 2) naRuntimeError(c, "setprop() expects at least 2 arguments");
252     for(int i=0; i<argc-1; i++) {
253         naRef s = naStringValue(c, args[i]);
254         if(naIsNil(s)) return naNil();
255         strncpy(p, naStr_data(s), buflen);
256         p += naStr_len(s);
257         buflen = BUFLEN - (p - buf);
258         if(i < (argc-2) && buflen > 0) {
259             *p++ = '/';
260             buflen--;
261         }
262     }
263
264     SGPropertyNode* props = globals->get_props();
265     naRef val = args[argc-1];
266     bool result = false;
267     try {
268         if(naIsString(val)) result = props->setStringValue(buf, naStr_data(val));
269         else {
270             naRef n = naNumValue(val);
271             if(naIsNil(n))
272                 naRuntimeError(c, "setprop() value is not string or number");
273                 
274             if (osg::isNaN(n.num)) {
275                 naRuntimeError(c, "setprop() passed a NaN");
276             }
277             
278             result = props->setDoubleValue(buf, n.num);
279         }
280     } catch (const string& err) {
281         naRuntimeError(c, (char *)err.c_str());
282     }
283     return naNum(result);
284 #undef BUFLEN
285 }
286
287 // print() extension function.  Concatenates and prints its arguments
288 // to the FlightGear log.  Uses the highest log level (SG_ALERT), to
289 // make sure it appears.  Is there better way to do this?
290 static naRef f_print(naContext c, naRef me, int argc, naRef* args)
291 {
292     string buf;
293     int n = argc;
294     for(int i=0; i<n; i++) {
295         naRef s = naStringValue(c, args[i]);
296         if(naIsNil(s)) continue;
297         buf += naStr_data(s);
298     }
299     SG_LOG(SG_NASAL, SG_ALERT, buf);
300     return naNum(buf.length());
301 }
302
303 // fgcommand() extension function.  Executes a named command via the
304 // FlightGear command manager.  Takes a single property node name as
305 // an argument.
306 static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
307 {
308     naRef cmd = argc > 0 ? args[0] : naNil();
309     naRef props = argc > 1 ? args[1] : naNil();
310     if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
311         naRuntimeError(c, "bad arguments to fgcommand()");
312     SGPropertyNode_ptr tmp, *node;
313     if(!naIsNil(props))
314         node = (SGPropertyNode_ptr*)naGhost_ptr(props);
315     else {
316         tmp = new SGPropertyNode();
317         node = &tmp;
318     }
319     return naNum(globals->get_commands()->execute(naStr_data(cmd), *node));
320 }
321
322 // settimer(func, dt, simtime) extension function.  Falls through to
323 // FGNasalSys::setTimer().  See there for docs.
324 static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
325 {
326     nasalSys->setTimer(c, argc, args);
327     return naNil();
328 }
329
330 // setlistener(func, property, bool) extension function.  Falls through to
331 // FGNasalSys::setListener().  See there for docs.
332 static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
333 {
334     return nasalSys->setListener(c, argc, args);
335 }
336
337 // removelistener(int) extension function. Falls through to
338 // FGNasalSys::removeListener(). See there for docs.
339 static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
340 {
341     return nasalSys->removeListener(c, argc, args);
342 }
343
344 // Returns a ghost handle to the argument to the currently executing
345 // command
346 static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
347 {
348     return nasalSys->cmdArgGhost();
349 }
350
351 // Sets up a property interpolation.  The first argument is either a
352 // ghost (SGPropertyNode_ptr*) or a string (global property path) to
353 // interpolate.  The second argument is a vector of pairs of
354 // value/delta numbers.
355 static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
356 {
357     SGPropertyNode* node;
358     naRef prop = argc > 0 ? args[0] : naNil();
359     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
360     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
361     else return naNil();
362
363     naRef curve = argc > 1 ? args[1] : naNil();
364     if(!naIsVector(curve)) return naNil();
365     int nPoints = naVec_size(curve) / 2;
366     double* values = new double[nPoints];
367     double* deltas = new double[nPoints];
368     for(int i=0; i<nPoints; i++) {
369         values[i] = naNumValue(naVec_get(curve, 2*i)).num;
370         deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num;
371     }
372
373     ((SGInterpolator*)globals->get_subsystem_mgr()
374         ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator"))
375         ->interpolate(node, nPoints, values, deltas);
376
377     delete[] values;
378     delete[] deltas;
379     return naNil();
380 }
381
382 // This is a better RNG than the one in the default Nasal distribution
383 // (which is based on the C library rand() implementation). It will
384 // override.
385 static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
386 {
387     return naNum(sg_random());
388 }
389
390 static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
391 {
392     sg_srandom_time();
393     return naNum(0);
394 }
395
396 static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
397 {
398     abort();
399     return naNil();
400 }
401
402 // Return an array listing of all files in a directory
403 static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
404 {
405     if(argc != 1 || !naIsString(args[0]))
406         naRuntimeError(c, "bad arguments to directory()");
407     
408     simgear::Dir d(SGPath(naStr_data(args[0])));
409     if(!d.exists()) return naNil();
410     naRef result = naNewVector(c);
411
412     simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
413     for (unsigned int i=0; i<paths.size(); ++i) {
414       std::string p = paths[i].file();
415       naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
416     }
417     
418     return result;
419 }
420
421 /**
422  * Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory
423  */
424 static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef* args)
425 {
426     if(argc != 1 || !naIsString(args[0]))
427         naRuntimeError(c, "bad arguments to resolveDataPath()");
428
429     SGPath p = globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
430     const char* pdata = p.c_str();
431     return naStr_fromdata(naNewString(c), const_cast<char*>(pdata), strlen(pdata));
432 }
433
434 // Parse XML file.
435 //     parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
436 //
437 // <path>      ... absolute path to an XML file
438 // <start-tag> ... callback function with two args: tag name, attribute hash
439 // <end-tag>   ... callback function with one arg:  tag name
440 // <data>      ... callback function with one arg:  data
441 // <pi>        ... callback function with two args: target, data
442 //                 (pi = "processing instruction")
443 // All four callback functions are optional and default to nil.
444 // The function returns nil on error, or the validated file name otherwise.
445 static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
446 {
447     if(argc < 1 || !naIsString(args[0]))
448         naRuntimeError(c, "parsexml(): path argument missing or not a string");
449     if(argc > 5) argc = 5;
450     for(int i=1; i<argc; i++)
451         if(!(naIsNil(args[i]) || naIsFunc(args[i])))
452             naRuntimeError(c, "parsexml(): callback argument not a function");
453
454     const char* file = fgValidatePath(naStr_data(args[0]), false);
455     if(!file) {
456         naRuntimeError(c, "parsexml(): reading '%s' denied "
457                 "(unauthorized access)", naStr_data(args[0]));
458         return naNil();
459     }
460     std::ifstream input(file);
461     NasalXMLVisitor visitor(c, argc, args);
462     try {
463         readXML(input, visitor);
464     } catch (const sg_exception& e) {
465         naRuntimeError(c, "parsexml(): file '%s' %s",
466                 file, e.getFormattedMessage().c_str());
467         return naNil();
468     }
469     return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
470 }
471
472 // Return UNIX epoch time in seconds.
473 static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
474 {
475 #ifdef _WIN32
476     FILETIME ft;
477     GetSystemTimeAsFileTime(&ft);
478     double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
479     // Converts from 100ns units in 1601 epoch to unix epoch in sec
480     return naNum((t * 1e-7) - 11644473600.0);
481 #else
482     struct timeval td;
483     gettimeofday(&td, 0);
484     return naNum(td.tv_sec + 1e-6 * td.tv_usec);
485 #endif
486 }
487
488 // Table of extension functions.  Terminate with zeros.
489 static struct { const char* name; naCFunction func; } funcs[] = {
490     { "getprop",   f_getprop },
491     { "setprop",   f_setprop },
492     { "print",     f_print },
493     { "_fgcommand", f_fgcommand },
494     { "settimer",  f_settimer },
495     { "_setlistener", f_setlistener },
496     { "removelistener", f_removelistener },
497     { "_cmdarg",  f_cmdarg },
498     { "_interpolate",  f_interpolate },
499     { "rand",  f_rand },
500     { "srand",  f_srand },
501     { "abort", f_abort },
502     { "directory", f_directory },
503     { "resolvepath", f_resolveDataPath },
504     { "parsexml", f_parsexml },
505     { "systime", f_systime },
506     { 0, 0 }
507 };
508
509 naRef FGNasalSys::cmdArgGhost()
510 {
511     return propNodeGhost(_cmdArg);
512 }
513
514 void FGNasalSys::init()
515 {
516     int i;
517
518     _context = naNewContext();
519
520     // Start with globals.  Add it to itself as a recursive
521     // sub-reference under the name "globals".  This gives client-code
522     // write access to the namespace if someone wants to do something
523     // fancy.
524     _globals = naInit_std(_context);
525     naSave(_context, _globals);
526     hashset(_globals, "globals", _globals);
527
528     hashset(_globals, "math", naInit_math(_context));
529     hashset(_globals, "bits", naInit_bits(_context));
530     hashset(_globals, "io", naInit_io(_context));
531     hashset(_globals, "thread", naInit_thread(_context));
532     hashset(_globals, "utf8", naInit_utf8(_context));
533
534     // Add our custom extension functions:
535     for(i=0; funcs[i].name; i++)
536         hashset(_globals, funcs[i].name,
537                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
538
539
540   
541     // And our SGPropertyNode wrapper
542     hashset(_globals, "props", genPropsModule());
543
544     // Make a "__gcsave" hash to hold the naRef objects which get
545     // passed to handles outside the interpreter (to protect them from
546     // begin garbage-collected).
547     _gcHash = naNewHash(_context);
548     hashset(_globals, "__gcsave", _gcHash);
549
550     initNasalPositioned(_globals, _context, _gcHash);
551   
552     // Now load the various source files in the Nasal directory
553     simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
554     loadScriptDirectory(nasalDir);
555
556     // Add modules in Nasal subdirectories to property tree
557     simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
558             simgear::Dir::NO_DOT_OR_DOTDOT, "");
559     for (unsigned int i=0; i<directories.size(); ++i) {
560         simgear::Dir dir(directories[i]);
561         simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
562         addModule(directories[i].file(), scripts);
563     }
564
565     // set signal and remove node to avoid restoring at reinit
566     const char *s = "nasal-dir-initialized";
567     SGPropertyNode *signal = fgGetNode("/sim/signals", true);
568     signal->setBoolValue(s, true);
569     signal->removeChildren(s, false);
570
571     // Pull scripts out of the property tree, too
572     loadPropertyScripts();
573   
574     // now Nasal modules are loaded, we can do some delayed work
575     postinitNasalPositioned(_globals, _context);
576 }
577
578 void FGNasalSys::update(double)
579 {
580     if(!_dead_listener.empty()) {
581         vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
582         for(it = _dead_listener.begin(); it != end; ++it) delete *it;
583         _dead_listener.clear();
584     }
585
586     if (!_loadList.empty())
587     {
588         // process Nasal load hook (only one per update loop to avoid excessive lags)
589         _loadList.pop()->load();
590     }
591     else
592     if (!_unloadList.empty())
593     {
594         // process pending Nasal unload hooks after _all_ load hooks were processed
595         // (only unload one per update loop to avoid excessive lags)
596         _unloadList.pop()->unload();
597     }
598
599     // The global context is a legacy thing.  We use dynamically
600     // created contexts for naCall() now, so that we can call them
601     // recursively.  But there are still spots that want to use it for
602     // naNew*() calls, which end up leaking memory because the context
603     // only clears out its temporary vector when it's *used*.  So just
604     // junk it and fetch a new/reinitialized one every frame.  This is
605     // clumsy: the right solution would use the dynamic context in all
606     // cases and eliminate _context entirely.  But that's more work,
607     // and this works fine (yes, they say "New" and "Free", but
608     // they're very fast, just trust me). -Andy
609     naFreeContext(_context);
610     _context = naNewContext();
611 }
612
613 bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
614 {
615   return p1.file() < p2.file();
616 }
617
618 // Loads all scripts in given directory 
619 void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir)
620 {
621     simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
622     // sort scripts, avoid loading sequence effects due to file system's
623     // random directory order
624     std::sort(scripts.begin(), scripts.end(), pathSortPredicate);
625
626     for (unsigned int i=0; i<scripts.size(); ++i) {
627       SGPath fullpath(scripts[i]);
628       SGPath file = fullpath.file();
629       loadModule(fullpath, file.base().c_str());
630     }
631 }
632
633 // Create module with list of scripts
634 void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
635 {
636     if (scripts.size()>0)
637     {
638         SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
639         SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
640         for (unsigned int i=0; i<scripts.size(); ++i) {
641             SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
642             pFileNode->setStringValue(scripts[i].c_str());
643         }
644         if (!module_node->hasChild("enabled",0))
645         {
646             SGPropertyNode* node = module_node->getChild("enabled",0,true);
647             node->setBoolValue(true);
648             node->setAttribute(SGPropertyNode::USERARCHIVE,true);
649         }
650     }
651 }
652
653 // Loads the scripts found under /nasal in the global tree
654 void FGNasalSys::loadPropertyScripts()
655 {
656     SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
657     if(!nasal) return;
658
659     for(int i=0; i<nasal->nChildren(); i++)
660     {
661         SGPropertyNode* n = nasal->getChild(i);
662         loadPropertyScripts(n);
663     }
664 }
665
666 // Loads the scripts found under /nasal in the global tree
667 void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
668 {
669     bool is_loaded = false;
670
671     const char* module = n->getName();
672     if(n->hasChild("module"))
673         module = n->getStringValue("module");
674     if (n->getBoolValue("enabled",true))
675     {
676         // allow multiple files to be specified within a single
677         // Nasal module tag
678         int j = 0;
679         SGPropertyNode *fn;
680         bool file_specified = false;
681         bool ok=true;
682         while((fn = n->getChild("file", j)) != NULL) {
683             file_specified = true;
684             const char* file = fn->getStringValue();
685             SGPath p(file);
686             if (!p.isAbsolute() || !p.exists())
687             {
688                 p = globals->resolve_maybe_aircraft_path(file);
689                 if (p.isNull())
690                 {
691                     SG_LOG(SG_NASAL, SG_ALERT, "Cannot find Nasal script '" <<
692                             file << "' for module '" << module << "'.");
693                 }
694             }
695             ok &= p.isNull() ? false : loadModule(p, module);
696             j++;
697         }
698
699         const char* src = n->getStringValue("script");
700         if(!n->hasChild("script")) src = 0; // Hrm...
701         if(src)
702             createModule(module, n->getPath().c_str(), src, strlen(src));
703
704         if(!file_specified && !src)
705         {
706             // module no longer exists - clear the archived "enable" flag
707             n->setAttribute(SGPropertyNode::USERARCHIVE,false);
708             SGPropertyNode* node = n->getChild("enabled",0,false);
709             if (node)
710                 node->setAttribute(SGPropertyNode::USERARCHIVE,false);
711
712             SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
713                     "no <file> or <script> defined in " <<
714                     "/nasal/" << module);
715         }
716         else
717             is_loaded = ok;
718     }
719     else
720     {
721         SGPropertyNode* enable = n->getChild("enabled");
722         if (enable)
723         {
724             FGNasalModuleListener* listener = new FGNasalModuleListener(n);
725             enable->addChangeListener(listener, false);
726         }
727     }
728     SGPropertyNode* loaded = n->getChild("loaded",0,true);
729     loaded->setAttribute(SGPropertyNode::PRESERVE,true);
730     loaded->setBoolValue(is_loaded);
731 }
732
733 // Logs a runtime error, with stack trace, to the FlightGear log stream
734 void FGNasalSys::logError(naContext context)
735 {
736     SG_LOG(SG_NASAL, SG_ALERT,
737            "Nasal runtime error: " << naGetError(context));
738     SG_LOG(SG_NASAL, SG_ALERT,
739            "  at " << naStr_data(naGetSourceFile(context, 0)) <<
740            ", line " << naGetLine(context, 0));
741     for(int i=1; i<naStackDepth(context); i++)
742         SG_LOG(SG_NASAL, SG_ALERT,
743                "  called from: " << naStr_data(naGetSourceFile(context, i)) <<
744                ", line " << naGetLine(context, i));
745 }
746
747 // Reads a script file, executes it, and places the resulting
748 // namespace into the global namespace under the specified module
749 // name.
750 bool FGNasalSys::loadModule(SGPath file, const char* module)
751 {
752     int len = 0;
753     char* buf = readfile(file.c_str(), &len);
754     if(!buf) {
755         SG_LOG(SG_NASAL, SG_ALERT,
756                "Nasal error: could not read script file " << file.c_str()
757                << " into module " << module);
758         return false;
759     }
760
761     bool ok = createModule(module, file.c_str(), buf, len);
762     delete[] buf;
763     return ok;
764 }
765
766 // Parse and run.  Save the local variables namespace, as it will
767 // become a sub-object of globals.  The optional "arg" argument can be
768 // used to pass an associated property node to the module, which can then
769 // be accessed via cmdarg().  (This is, for example, used by XML dialogs.)
770 bool FGNasalSys::createModule(const char* moduleName, const char* fileName,
771                               const char* src, int len,
772                               const SGPropertyNode* cmdarg,
773                               int argc, naRef* args)
774 {
775     naRef code = parse(fileName, src, len);
776     if(naIsNil(code))
777         return false;
778
779     // See if we already have a module hash to use.  This allows the
780     // user to, for example, add functions to the built-in math
781     // module.  Make a new one if necessary.
782     naRef locals;
783     naRef modname = naNewString(_context);
784     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
785     if(!naHash_get(_globals, modname, &locals))
786         locals = naNewHash(_context);
787
788     _cmdArg = (SGPropertyNode*)cmdarg;
789
790     call(code, argc, args, locals);
791     hashset(_globals, moduleName, locals);
792     return true;
793 }
794
795 void FGNasalSys::deleteModule(const char* moduleName)
796 {
797     naRef modname = naNewString(_context);
798     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
799     naHash_delete(_globals, modname);
800 }
801
802 naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
803 {
804     int errLine = -1;
805     naRef srcfile = naNewString(_context);
806     naStr_fromdata(srcfile, (char*)filename, strlen(filename));
807     naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
808     if(naIsNil(code)) {
809         SG_LOG(SG_NASAL, SG_ALERT,
810                "Nasal parse error: " << naGetError(_context) <<
811                " in "<< filename <<", line " << errLine);
812         return naNil();
813     }
814
815     // Bind to the global namespace before returning
816     return naBindFunction(_context, code, _globals);
817 }
818
819 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
820 {
821     const char* nasal = arg->getStringValue("script");
822     const char* moduleName = arg->getStringValue("module");
823     naRef code = parse(arg->getPath(true).c_str(), nasal, strlen(nasal));
824     if(naIsNil(code)) return false;
825
826     // Commands can be run "in" a module.  Make sure that module
827     // exists, and set it up as the local variables hash for the
828     // command.
829     naRef locals = naNil();
830     if(moduleName[0]) {
831         naRef modname = naNewString(_context);
832         naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
833         if(!naHash_get(_globals, modname, &locals)) {
834             locals = naNewHash(_context);
835             naHash_set(_globals, modname, locals);
836         }
837     }
838
839     // Cache this command's argument for inspection via cmdarg().  For
840     // performance reasons, we won't bother with it if the invoked
841     // code doesn't need it.
842     _cmdArg = (SGPropertyNode*)arg;
843
844     call(code, 0, 0, locals);
845     return true;
846 }
847
848 // settimer(func, dt, simtime) extension function.  The first argument
849 // is a Nasal function to call, the second is a delta time (from now),
850 // in seconds.  The third, if present, is a boolean value indicating
851 // that "real world" time (rather than simulator time) is to be used.
852 //
853 // Implementation note: the FGTimer objects don't live inside the
854 // garbage collector, so the Nasal handler functions have to be
855 // "saved" somehow lest they be inadvertently cleaned.  In this case,
856 // they are inserted into a globals.__gcsave hash and removed on
857 // expiration.
858 void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
859 {
860     // Extract the handler, delta, and simtime arguments:
861     naRef handler = argc > 0 ? args[0] : naNil();
862     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
863         naRuntimeError(c, "settimer() with invalid function argument");
864         return;
865     }
866
867     naRef delta = argc > 1 ? args[1] : naNil();
868     if(naIsNil(delta)) {
869         naRuntimeError(c, "settimer() with invalid time argument");
870         return;
871     }
872
873     bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
874
875     // Generate and register a C++ timer handler
876     NasalTimer* t = new NasalTimer;
877     t->handler = handler;
878     t->gcKey = gcSave(handler);
879     t->nasal = this;
880
881     globals->get_event_mgr()->addEvent("NasalTimer",
882                                        t, &NasalTimer::timerExpired,
883                                        delta.num, simtime);
884 }
885
886 void FGNasalSys::handleTimer(NasalTimer* t)
887 {
888     call(t->handler, 0, 0, naNil());
889     gcRelease(t->gcKey);
890 }
891
892 int FGNasalSys::gcSave(naRef r)
893 {
894     int key = _nextGCKey++;
895     naHash_set(_gcHash, naNum(key), r);
896     return key;
897 }
898
899 void FGNasalSys::gcRelease(int key)
900 {
901     naHash_delete(_gcHash, naNum(key));
902 }
903
904 void FGNasalSys::NasalTimer::timerExpired()
905 {
906     nasal->handleTimer(this);
907     delete this;
908 }
909
910 int FGNasalSys::_listenerId = 0;
911
912 // setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
913 // Attaches a callback function to a property (specified as a global
914 // property path string or a SGPropertyNode_ptr* ghost). If the third,
915 // optional argument (default=0) is set to 1, then the function is also
916 // called initially. If the fourth, optional argument is set to 0, then the
917 // function is only called when the property node value actually changes.
918 // Otherwise it's called independent of the value whenever the node is
919 // written to (default). The setlistener() function returns a unique
920 // id number, which is to be used as argument to the removelistener()
921 // function.
922 naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
923 {
924     SGPropertyNode_ptr node;
925     naRef prop = argc > 0 ? args[0] : naNil();
926     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
927     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
928     else {
929         naRuntimeError(c, "setlistener() with invalid property argument");
930         return naNil();
931     }
932
933     if(node->isTied())
934         SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
935                 node->getPath());
936
937     naRef code = argc > 1 ? args[1] : naNil();
938     if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
939         naRuntimeError(c, "setlistener() with invalid function argument");
940         return naNil();
941     }
942
943     int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
944     int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
945     FGNasalListener *nl = new FGNasalListener(node, code, this,
946             gcSave(code), _listenerId, init, type);
947
948     node->addChangeListener(nl, init != 0);
949
950     _listener[_listenerId] = nl;
951     return naNum(_listenerId++);
952 }
953
954 // removelistener(int) extension function. The argument is the id of
955 // a listener as returned by the setlistener() function.
956 naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
957 {
958     naRef id = argc > 0 ? args[0] : naNil();
959     map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
960
961     if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
962         naRuntimeError(c, "removelistener() with invalid listener id");
963         return naNil();
964     }
965
966     it->second->_dead = true;
967     _dead_listener.push_back(it->second);
968     _listener.erase(it);
969     return naNum(_listener.size());
970 }
971
972
973
974 // FGNasalListener class.
975
976 FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
977                                  FGNasalSys* nasal, int key, int id,
978                                  int init, int type) :
979     _node(node),
980     _code(code),
981     _gcKey(key),
982     _id(id),
983     _nas(nasal),
984     _init(init),
985     _type(type),
986     _active(0),
987     _dead(false),
988     _last_int(0L),
989     _last_float(0.0)
990 {
991     if(_type == 0 && !_init)
992         changed(node);
993 }
994
995 FGNasalListener::~FGNasalListener()
996 {
997     _node->removeChangeListener(this);
998     _nas->gcRelease(_gcKey);
999 }
1000
1001 void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1002 {
1003     if(_active || _dead) return;
1004     SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
1005     _active++;
1006     naRef arg[4];
1007     arg[0] = _nas->propNodeGhost(which);
1008     arg[1] = _nas->propNodeGhost(_node);
1009     arg[2] = mode;                  // value changed, child added/removed
1010     arg[3] = naNum(_node != which); // child event?
1011     _nas->call(_code, 4, arg, naNil());
1012     _active--;
1013 }
1014
1015 void FGNasalListener::valueChanged(SGPropertyNode* node)
1016 {
1017     if(_type < 2 && node != _node) return;   // skip child events
1018     if(_type > 0 || changed(_node) || _init)
1019         call(node, naNum(0));
1020
1021     _init = 0;
1022 }
1023
1024 void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
1025 {
1026     if(_type == 2) call(child, naNum(1));
1027 }
1028
1029 void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
1030 {
1031     if(_type == 2) call(child, naNum(-1));
1032 }
1033
1034 bool FGNasalListener::changed(SGPropertyNode* node)
1035 {
1036     using namespace simgear;
1037     props::Type type = node->getType();
1038     if(type == props::NONE) return false;
1039     if(type == props::UNSPECIFIED) return true;
1040
1041     bool result;
1042     switch(type) {
1043     case props::BOOL:
1044     case props::INT:
1045     case props::LONG:
1046         {
1047             long l = node->getLongValue();
1048             result = l != _last_int;
1049             _last_int = l;
1050             return result;
1051         }
1052     case props::FLOAT:
1053     case props::DOUBLE:
1054         {
1055             double d = node->getDoubleValue();
1056             result = d != _last_float;
1057             _last_float = d;
1058             return result;
1059         }
1060     default:
1061         {
1062             string s = node->getStringValue();
1063             result = s != _last_string;
1064             _last_string = s;
1065             return result;
1066         }
1067     }
1068 }
1069
1070
1071
1072 // FGNasalModelData class.  If sgLoad3DModel() is called with a pointer to
1073 // such a class, then it lets modelLoaded() run the <load> script, and the
1074 // destructor the <unload> script. The latter happens when the model branch
1075 // is removed from the scene graph.
1076
1077 unsigned int FGNasalModelData::_module_id = 0;
1078
1079 void FGNasalModelData::load()
1080 {
1081     std::stringstream m;
1082     m << "__model" << _module_id++;
1083     _module = m.str();
1084
1085     SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str());
1086
1087     const char *s = _load ? _load->getStringValue() : "";
1088
1089     naRef arg[2];
1090     arg[0] = nasalSys->propNodeGhost(_root);
1091     arg[1] = nasalSys->propNodeGhost(_prop);
1092     nasalSys->createModule(_module.c_str(), _path.c_str(), s, strlen(s),
1093                            _root, 2, arg);
1094 }
1095
1096 void FGNasalModelData::unload()
1097 {
1098     if (_module.empty())
1099         return;
1100
1101     if(!nasalSys) {
1102         SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
1103                 "without Nasal subsystem present.");
1104         return;
1105     }
1106
1107     SG_LOG(SG_NASAL, SG_DEBUG, "Unloading nasal module " << _module.c_str());
1108
1109     if (_unload)
1110     {
1111         const char *s = _unload->getStringValue();
1112         nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root);
1113     }
1114
1115     nasalSys->deleteModule(_module.c_str());
1116 }
1117
1118 void FGNasalModelDataProxy::modelLoaded(const string& path, SGPropertyNode *prop,
1119                                    osg::Node *)
1120 {
1121     if(!nasalSys) {
1122         SG_LOG(SG_NASAL, SG_WARN, "Trying to run a <load> script "
1123                 "without Nasal subsystem present.");
1124         return;
1125     }
1126
1127     if(!prop)
1128         return;
1129
1130     SGPropertyNode *nasal = prop->getNode("nasal");
1131     if(!nasal)
1132         return;
1133
1134     SGPropertyNode* load   = nasal->getNode("load");
1135     SGPropertyNode* unload = nasal->getNode("unload");
1136
1137     if ((!load) && (!unload))
1138         return;
1139
1140     _data = new FGNasalModelData(_root, path, prop, load, unload);
1141
1142     // register Nasal module to be created and loaded in the main thread.
1143     nasalSys->registerToLoad(_data);
1144 }
1145
1146 FGNasalModelDataProxy::~FGNasalModelDataProxy()
1147 {
1148     // when necessary, register Nasal module to be destroyed/unloaded
1149     // in the main thread.
1150     if ((_data.valid())&&(nasalSys))
1151         nasalSys->registerToUnload(_data);
1152 }
1153
1154 // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
1155 //
1156 NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
1157     _c(naSubContext(c)),
1158     _start_element(argc > 1 ? args[1] : naNil()),
1159     _end_element(argc > 2 ? args[2] : naNil()),
1160     _data(argc > 3 ? args[3] : naNil()),
1161     _pi(argc > 4 ? args[4] : naNil())
1162 {
1163 }
1164
1165 void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
1166 {
1167     if(naIsNil(_start_element)) return;
1168     naRef attr = naNewHash(_c);
1169     for(int i=0; i<a.size(); i++) {
1170         naRef name = make_string(a.getName(i));
1171         naRef value = make_string(a.getValue(i));
1172         naHash_set(attr, name, value);
1173     }
1174     call(_start_element, 2, make_string(tag), attr);
1175 }
1176
1177 void NasalXMLVisitor::endElement(const char* tag)
1178 {
1179     if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
1180 }
1181
1182 void NasalXMLVisitor::data(const char* str, int len)
1183 {
1184     if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
1185 }
1186
1187 void NasalXMLVisitor::pi(const char* target, const char* data)
1188 {
1189     if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
1190 }
1191
1192 void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
1193 {
1194     naRef args[2];
1195     args[0] = a;
1196     args[1] = b;
1197     naCall(_c, func, num, args, naNil(), naNil());
1198     if(naGetError(_c))
1199         naRethrowError(_c);
1200 }
1201
1202 naRef NasalXMLVisitor::make_string(const char* s, int n)
1203 {
1204     return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
1205                           n < 0 ? strlen(s) : n);
1206 }
1207
1208