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