]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
b65264a8be78a87da896542044c9fa6c4f1c770d
[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 char* moduleName,
820                                 const char* fileName,
821                                 const char* src,
822                                 const SGPropertyNode* arg )
823 {
824     naRef code = parse(fileName, src, strlen(src));
825     if(naIsNil(code)) return false;
826
827     // Commands can be run "in" a module.  Make sure that module
828     // exists, and set it up as the local variables hash for the
829     // command.
830     naRef locals = naNil();
831     if(moduleName[0]) {
832         naRef modname = naNewString(_context);
833         naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
834         if(!naHash_get(_globals, modname, &locals)) {
835             locals = naNewHash(_context);
836             naHash_set(_globals, modname, locals);
837         }
838     }
839
840     // Cache this command's argument for inspection via cmdarg().  For
841     // performance reasons, we won't bother with it if the invoked
842     // code doesn't need it.
843     _cmdArg = (SGPropertyNode*)arg;
844
845     call(code, 0, 0, locals);
846     return true;
847 }
848
849 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
850 {
851   const char* src = arg->getStringValue("script");
852   const char* moduleName = arg->getStringValue("module");
853
854   return handleCommand( moduleName,
855                         arg ? arg->getPath(true).c_str() : moduleName,
856                         src,
857                         arg );
858 }
859
860 // settimer(func, dt, simtime) extension function.  The first argument
861 // is a Nasal function to call, the second is a delta time (from now),
862 // in seconds.  The third, if present, is a boolean value indicating
863 // that "real world" time (rather than simulator time) is to be used.
864 //
865 // Implementation note: the FGTimer objects don't live inside the
866 // garbage collector, so the Nasal handler functions have to be
867 // "saved" somehow lest they be inadvertently cleaned.  In this case,
868 // they are inserted into a globals.__gcsave hash and removed on
869 // expiration.
870 void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
871 {
872     // Extract the handler, delta, and simtime arguments:
873     naRef handler = argc > 0 ? args[0] : naNil();
874     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
875         naRuntimeError(c, "settimer() with invalid function argument");
876         return;
877     }
878
879     naRef delta = argc > 1 ? args[1] : naNil();
880     if(naIsNil(delta)) {
881         naRuntimeError(c, "settimer() with invalid time argument");
882         return;
883     }
884
885     bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
886
887     // Generate and register a C++ timer handler
888     NasalTimer* t = new NasalTimer;
889     t->handler = handler;
890     t->gcKey = gcSave(handler);
891     t->nasal = this;
892
893     globals->get_event_mgr()->addEvent("NasalTimer",
894                                        t, &NasalTimer::timerExpired,
895                                        delta.num, simtime);
896 }
897
898 void FGNasalSys::handleTimer(NasalTimer* t)
899 {
900     call(t->handler, 0, 0, naNil());
901     gcRelease(t->gcKey);
902 }
903
904 int FGNasalSys::gcSave(naRef r)
905 {
906     int key = _nextGCKey++;
907     naHash_set(_gcHash, naNum(key), r);
908     return key;
909 }
910
911 void FGNasalSys::gcRelease(int key)
912 {
913     naHash_delete(_gcHash, naNum(key));
914 }
915
916 void FGNasalSys::NasalTimer::timerExpired()
917 {
918     nasal->handleTimer(this);
919     delete this;
920 }
921
922 int FGNasalSys::_listenerId = 0;
923
924 // setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
925 // Attaches a callback function to a property (specified as a global
926 // property path string or a SGPropertyNode_ptr* ghost). If the third,
927 // optional argument (default=0) is set to 1, then the function is also
928 // called initially. If the fourth, optional argument is set to 0, then the
929 // function is only called when the property node value actually changes.
930 // Otherwise it's called independent of the value whenever the node is
931 // written to (default). The setlistener() function returns a unique
932 // id number, which is to be used as argument to the removelistener()
933 // function.
934 naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
935 {
936     SGPropertyNode_ptr node;
937     naRef prop = argc > 0 ? args[0] : naNil();
938     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
939     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
940     else {
941         naRuntimeError(c, "setlistener() with invalid property argument");
942         return naNil();
943     }
944
945     if(node->isTied())
946         SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
947                 node->getPath());
948
949     naRef code = argc > 1 ? args[1] : naNil();
950     if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
951         naRuntimeError(c, "setlistener() with invalid function argument");
952         return naNil();
953     }
954
955     int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
956     int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
957     FGNasalListener *nl = new FGNasalListener(node, code, this,
958             gcSave(code), _listenerId, init, type);
959
960     node->addChangeListener(nl, init != 0);
961
962     _listener[_listenerId] = nl;
963     return naNum(_listenerId++);
964 }
965
966 // removelistener(int) extension function. The argument is the id of
967 // a listener as returned by the setlistener() function.
968 naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
969 {
970     naRef id = argc > 0 ? args[0] : naNil();
971     map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
972
973     if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
974         naRuntimeError(c, "removelistener() with invalid listener id");
975         return naNil();
976     }
977
978     it->second->_dead = true;
979     _dead_listener.push_back(it->second);
980     _listener.erase(it);
981     return naNum(_listener.size());
982 }
983
984
985
986 // FGNasalListener class.
987
988 FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
989                                  FGNasalSys* nasal, int key, int id,
990                                  int init, int type) :
991     _node(node),
992     _code(code),
993     _gcKey(key),
994     _id(id),
995     _nas(nasal),
996     _init(init),
997     _type(type),
998     _active(0),
999     _dead(false),
1000     _last_int(0L),
1001     _last_float(0.0)
1002 {
1003     if(_type == 0 && !_init)
1004         changed(node);
1005 }
1006
1007 FGNasalListener::~FGNasalListener()
1008 {
1009     _node->removeChangeListener(this);
1010     _nas->gcRelease(_gcKey);
1011 }
1012
1013 void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1014 {
1015     if(_active || _dead) return;
1016     SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
1017     _active++;
1018     naRef arg[4];
1019     arg[0] = _nas->propNodeGhost(which);
1020     arg[1] = _nas->propNodeGhost(_node);
1021     arg[2] = mode;                  // value changed, child added/removed
1022     arg[3] = naNum(_node != which); // child event?
1023     _nas->call(_code, 4, arg, naNil());
1024     _active--;
1025 }
1026
1027 void FGNasalListener::valueChanged(SGPropertyNode* node)
1028 {
1029     if(_type < 2 && node != _node) return;   // skip child events
1030     if(_type > 0 || changed(_node) || _init)
1031         call(node, naNum(0));
1032
1033     _init = 0;
1034 }
1035
1036 void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
1037 {
1038     if(_type == 2) call(child, naNum(1));
1039 }
1040
1041 void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
1042 {
1043     if(_type == 2) call(child, naNum(-1));
1044 }
1045
1046 bool FGNasalListener::changed(SGPropertyNode* node)
1047 {
1048     using namespace simgear;
1049     props::Type type = node->getType();
1050     if(type == props::NONE) return false;
1051     if(type == props::UNSPECIFIED) return true;
1052
1053     bool result;
1054     switch(type) {
1055     case props::BOOL:
1056     case props::INT:
1057     case props::LONG:
1058         {
1059             long l = node->getLongValue();
1060             result = l != _last_int;
1061             _last_int = l;
1062             return result;
1063         }
1064     case props::FLOAT:
1065     case props::DOUBLE:
1066         {
1067             double d = node->getDoubleValue();
1068             result = d != _last_float;
1069             _last_float = d;
1070             return result;
1071         }
1072     default:
1073         {
1074             string s = node->getStringValue();
1075             result = s != _last_string;
1076             _last_string = s;
1077             return result;
1078         }
1079     }
1080 }
1081
1082
1083
1084 // FGNasalModelData class.  If sgLoad3DModel() is called with a pointer to
1085 // such a class, then it lets modelLoaded() run the <load> script, and the
1086 // destructor the <unload> script. The latter happens when the model branch
1087 // is removed from the scene graph.
1088
1089 unsigned int FGNasalModelData::_module_id = 0;
1090
1091 void FGNasalModelData::load()
1092 {
1093     std::stringstream m;
1094     m << "__model" << _module_id++;
1095     _module = m.str();
1096
1097     SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str());
1098
1099     const char *s = _load ? _load->getStringValue() : "";
1100
1101     naRef arg[2];
1102     arg[0] = nasalSys->propNodeGhost(_root);
1103     arg[1] = nasalSys->propNodeGhost(_prop);
1104     nasalSys->createModule(_module.c_str(), _path.c_str(), s, strlen(s),
1105                            _root, 2, arg);
1106 }
1107
1108 void FGNasalModelData::unload()
1109 {
1110     if (_module.empty())
1111         return;
1112
1113     if(!nasalSys) {
1114         SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
1115                 "without Nasal subsystem present.");
1116         return;
1117     }
1118
1119     SG_LOG(SG_NASAL, SG_DEBUG, "Unloading nasal module " << _module.c_str());
1120
1121     if (_unload)
1122     {
1123         const char *s = _unload->getStringValue();
1124         nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root);
1125     }
1126
1127     nasalSys->deleteModule(_module.c_str());
1128 }
1129
1130 void FGNasalModelDataProxy::modelLoaded(const string& path, SGPropertyNode *prop,
1131                                    osg::Node *)
1132 {
1133     if(!nasalSys) {
1134         SG_LOG(SG_NASAL, SG_WARN, "Trying to run a <load> script "
1135                 "without Nasal subsystem present.");
1136         return;
1137     }
1138
1139     if(!prop)
1140         return;
1141
1142     SGPropertyNode *nasal = prop->getNode("nasal");
1143     if(!nasal)
1144         return;
1145
1146     SGPropertyNode* load   = nasal->getNode("load");
1147     SGPropertyNode* unload = nasal->getNode("unload");
1148
1149     if ((!load) && (!unload))
1150         return;
1151
1152     _data = new FGNasalModelData(_root, path, prop, load, unload);
1153
1154     // register Nasal module to be created and loaded in the main thread.
1155     nasalSys->registerToLoad(_data);
1156 }
1157
1158 FGNasalModelDataProxy::~FGNasalModelDataProxy()
1159 {
1160     // when necessary, register Nasal module to be destroyed/unloaded
1161     // in the main thread.
1162     if ((_data.valid())&&(nasalSys))
1163         nasalSys->registerToUnload(_data);
1164 }
1165
1166 // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
1167 //
1168 NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
1169     _c(naSubContext(c)),
1170     _start_element(argc > 1 ? args[1] : naNil()),
1171     _end_element(argc > 2 ? args[2] : naNil()),
1172     _data(argc > 3 ? args[3] : naNil()),
1173     _pi(argc > 4 ? args[4] : naNil())
1174 {
1175 }
1176
1177 void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
1178 {
1179     if(naIsNil(_start_element)) return;
1180     naRef attr = naNewHash(_c);
1181     for(int i=0; i<a.size(); i++) {
1182         naRef name = make_string(a.getName(i));
1183         naRef value = make_string(a.getValue(i));
1184         naHash_set(attr, name, value);
1185     }
1186     call(_start_element, 2, make_string(tag), attr);
1187 }
1188
1189 void NasalXMLVisitor::endElement(const char* tag)
1190 {
1191     if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
1192 }
1193
1194 void NasalXMLVisitor::data(const char* str, int len)
1195 {
1196     if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
1197 }
1198
1199 void NasalXMLVisitor::pi(const char* target, const char* data)
1200 {
1201     if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
1202 }
1203
1204 void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
1205 {
1206     naRef args[2];
1207     args[0] = a;
1208     args[1] = b;
1209     naCall(_c, func, num, args, naNil(), naNil());
1210     if(naGetError(_c))
1211         naRethrowError(_c);
1212 }
1213
1214 naRef NasalXMLVisitor::make_string(const char* s, int n)
1215 {
1216     return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
1217                           n < 0 ? strlen(s) : n);
1218 }
1219
1220