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