]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalSys.cxx
Allow the second (property node) argument to fgcommand() to be nil or
[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
15 #include <plib/ul.h>
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/interpolator.hxx>
22 #include <simgear/structure/commands.hxx>
23
24 #include <Main/globals.hxx>
25 #include <Main/fg_props.hxx>
26
27 #include "NasalSys.hxx"
28
29 static FGNasalSys* nasalSys = 0;
30
31
32 // Read and return file contents in a single buffer.  Note use of
33 // stat() to get the file size.  This is a win32 function, believe it
34 // or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
35 // Text mode brain damage will kill us if we're trying to do bytewise
36 // I/O.
37 static char* readfile(const char* file, int* lenOut)
38 {
39     struct stat data;
40     if(stat(file, &data) != 0) return 0;
41     FILE* f = fopen(file, "rb");
42     if(!f) return 0;
43     char* buf = new char[data.st_size];
44     *lenOut = fread(buf, 1, data.st_size, f);
45     fclose(f);
46     if(*lenOut != data.st_size) {
47         // Shouldn't happen, but warn anyway since it represents a
48         // platform bug and not a typical runtime error (missing file,
49         // etc...)
50         SG_LOG(SG_NASAL, SG_ALERT,
51                "ERROR in Nasal initialization: " <<
52                "short count returned from fread() of " << file <<
53                ".  Check your C library!");
54         delete[] buf;
55         return 0;
56     }
57     return buf;
58 }
59
60 FGNasalSys::FGNasalSys()
61 {
62     nasalSys = this;
63     _context = 0;
64     _globals = naNil();
65     _gcHash = naNil();
66     _nextGCKey = 0; // Any value will do
67     _callCount = 0;
68     _purgeListeners = false;
69 }
70
71 // Does a naCall() in a new context.  Wrapped here to make lock
72 // tracking easier.  Extension functions are called with the lock, but
73 // we have to release it before making a new naCall().  So rather than
74 // drop the lock in every extension function that might call back into
75 // Nasal, we keep a stack depth counter here and only unlock/lock
76 // around the naCall if it isn't the first one.
77 naRef FGNasalSys::call(naRef code, naRef locals)
78 {
79     naContext ctx = naNewContext();
80     if(_callCount) naModUnlock();
81     _callCount++;
82     naRef result = naCall(ctx, code, 0, 0, naNil(), locals);
83     if(naGetError(ctx))
84         logError(ctx);
85     _callCount--;
86     if(_callCount) naModLock();
87     naFreeContext(ctx);
88     return result;
89 }
90
91 FGNasalSys::~FGNasalSys()
92 {
93     nasalSys = 0;
94     map<int, FGNasalListener *>::iterator it, end = _listener.end();
95     for(it = _listener.begin(); it != end; ++it)
96         delete it->second;
97
98     naFreeContext(_context);
99     _globals = naNil();
100 }
101
102 bool FGNasalSys::parseAndRun(const char* sourceCode)
103 {
104     naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
105                        strlen(sourceCode));
106     if(naIsNil(code))
107         return false;
108     call(code, naNil());
109     return true;
110 }
111
112 FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
113 {
114     FGNasalScript* script = new FGNasalScript();
115     script->_gcKey = -1; // important, if we delete it on a parse
116     script->_nas = this; // error, don't clobber a real handle!
117
118     char buf[256];
119     if(!name) {
120         sprintf(buf, "FGNasalScript@%p", (void *)script);
121         name = buf;
122     }
123
124     script->_code = parse(name, src, strlen(src));
125     if(naIsNil(script->_code)) {
126         delete script;
127         return 0;
128     }
129
130     script->_gcKey = gcSave(script->_code);
131     return script;
132 }
133
134 // Utility.  Sets a named key in a hash by C string, rather than nasal
135 // string object.
136 void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
137 {
138     naRef s = naNewString(_context);
139     naStr_fromdata(s, (char*)key, strlen(key));
140     naHash_set(hash, s, val);
141 }
142
143 // The get/setprop functions accept a *list* of strings and walk
144 // through the property tree with them to find the appropriate node.
145 // This allows a Nasal object to hold onto a property path and use it
146 // like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
147 // is the utility function that walks the property tree.
148 // Future enhancement: support integer arguments to specify array
149 // elements.
150 static SGPropertyNode* findnode(naContext c, naRef* vec, int len)
151 {
152     SGPropertyNode* p = globals->get_props();
153     try {
154         for(int i=0; i<len; i++) {
155             naRef a = vec[i];
156             if(!naIsString(a)) return 0;
157             p = p->getNode(naStr_data(a));
158             if(p == 0) return 0;
159         }
160     } catch (const string& err) {
161         naRuntimeError(c, (char *)err.c_str());
162         return 0;
163     }
164     return p;
165 }
166
167 // getprop() extension function.  Concatenates its string arguments as
168 // property names and returns the value of the specified property.  Or
169 // nil if it doesn't exist.
170 static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
171 {
172     const SGPropertyNode* p = findnode(c, args, argc);
173     if(!p) return naNil();
174
175     switch(p->getType()) {
176     case SGPropertyNode::BOOL:   case SGPropertyNode::INT:
177     case SGPropertyNode::LONG:   case SGPropertyNode::FLOAT:
178     case SGPropertyNode::DOUBLE:
179         return naNum(p->getDoubleValue());
180
181     case SGPropertyNode::STRING:
182     case SGPropertyNode::UNSPECIFIED:
183         {
184             naRef nastr = naNewString(c);
185             const char* val = p->getStringValue();
186             naStr_fromdata(nastr, (char*)val, strlen(val));
187             return nastr;
188         }
189     case SGPropertyNode::ALIAS: // <--- FIXME, recurse?
190     default:
191         return naNil();
192     }
193 }
194
195 // setprop() extension function.  Concatenates its string arguments as
196 // property names and sets the value of the specified property to the
197 // final argument.
198 static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
199 {
200 #define BUFLEN 1024
201     char buf[BUFLEN + 1];
202     buf[BUFLEN] = 0;
203     char* p = buf;
204     int buflen = BUFLEN;
205     for(int i=0; i<argc-1; i++) {
206         naRef s = naStringValue(c, args[i]);
207         if(naIsNil(s)) return naNil();
208         strncpy(p, naStr_data(s), buflen);
209         p += naStr_len(s);
210         buflen = BUFLEN - (p - buf);
211         if(i < (argc-2) && buflen > 0) {
212             *p++ = '/';
213             buflen--;
214         }
215     }
216
217     SGPropertyNode* props = globals->get_props();
218     naRef val = args[argc-1];
219     try {
220         if(naIsString(val)) props->setStringValue(buf, naStr_data(val));
221         else {
222             naRef n = naNumValue(val);
223             if(naIsNil(n))
224                 naRuntimeError(c, "setprop() value is not string or number");
225             props->setDoubleValue(buf, n.num);
226         }
227     } catch (const string& err) {
228         naRuntimeError(c, (char *)err.c_str());
229     }
230     return naNil();
231 #undef BUFLEN
232 }
233
234 // print() extension function.  Concatenates and prints its arguments
235 // to the FlightGear log.  Uses the highest log level (SG_ALERT), to
236 // make sure it appears.  Is there better way to do this?
237 static naRef f_print(naContext c, naRef me, int argc, naRef* args)
238 {
239     string buf;
240     int n = argc;
241     for(int i=0; i<n; i++) {
242         naRef s = naStringValue(c, args[i]);
243         if(naIsNil(s)) continue;
244         buf += naStr_data(s);
245     }
246     SG_LOG(SG_GENERAL, SG_ALERT, buf);
247     return naNum(buf.length());
248 }
249
250 // fgcommand() extension function.  Executes a named command via the
251 // FlightGear command manager.  Takes a single property node name as
252 // an argument.
253 static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
254 {
255     naRef cmd = argc > 0 ? args[0] : naNil();
256     naRef props = argc > 1 ? args[1] : naNil();
257     if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
258         naRuntimeError(c, "bad arguments to fgcommand()");
259     SGPropertyNode_ptr* node = NULL;
260     if(!naIsNil(props))
261         node = (SGPropertyNode_ptr*)naGhost_ptr(props);
262     return naNum(globals->get_commands()->execute(naStr_data(cmd), *node));
263 }
264
265 // settimer(func, dt, simtime) extension function.  Falls through to
266 // FGNasalSys::setTimer().  See there for docs.
267 static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
268 {
269     nasalSys->setTimer(c, argc, args);
270     return naNil();
271 }
272
273 // setlistener(func, property, bool) extension function.  Falls through to
274 // FGNasalSys::setListener().  See there for docs.
275 static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
276 {
277     return nasalSys->setListener(c, argc, args);
278 }
279
280 // removelistener(int) extension function. Falls through to
281 // FGNasalSys::removeListener(). See there for docs.
282 static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
283 {
284     return nasalSys->removeListener(c, argc, args);
285 }
286
287 // Returns a ghost handle to the argument to the currently executing
288 // command
289 static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
290 {
291     return nasalSys->cmdArgGhost();
292 }
293
294 // Sets up a property interpolation.  The first argument is either a
295 // ghost (SGPropertyNode_ptr*) or a string (global property path) to
296 // interpolate.  The second argument is a vector of pairs of
297 // value/delta numbers.
298 static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
299 {
300     SGPropertyNode* node;
301     naRef prop = argc > 0 ? args[0] : naNil();
302     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
303     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
304     else return naNil();
305
306     naRef curve = argc > 1 ? args[1] : naNil();
307     if(!naIsVector(curve)) return naNil();
308     int nPoints = naVec_size(curve) / 2;
309     double* values = new double[nPoints];
310     double* deltas = new double[nPoints];
311     for(int i=0; i<nPoints; i++) {
312         values[i] = naNumValue(naVec_get(curve, 2*i)).num;
313         deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num;
314     }
315
316     ((SGInterpolator*)globals->get_subsystem_mgr()
317         ->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator"))
318         ->interpolate(node, nPoints, values, deltas);
319
320     return naNil();
321 }
322
323 // This is a better RNG than the one in the default Nasal distribution
324 // (which is based on the C library rand() implementation). It will
325 // override.
326 static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
327 {
328     return naNum(sg_random());
329 }
330
331 static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
332 {
333     sg_srandom_time();
334     return naNum(0);
335 }
336
337 // Return an array listing of all files in a directory
338 static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
339 {
340     if(argc != 1 || !naIsString(args[0]))
341         naRuntimeError(c, "bad arguments to directory()");
342     naRef ldir = args[0];
343     ulDir* dir = ulOpenDir(naStr_data(args[0]));
344     if(!dir) return naNil();
345     naRef result = naNewVector(c);
346     ulDirEnt* dent;
347     while((dent = ulReadDir(dir)))
348         naVec_append(result, naStr_fromdata(naNewString(c), dent->d_name,
349                                             strlen(dent->d_name)));
350     ulCloseDir(dir);
351     return result;
352 }
353
354 // Return UNIX epoch time in seconds.
355 static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
356 {
357 #ifdef WIN32
358     FILETIME ft;
359     GetSystemTimeAsFileTime(&ft);
360     double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
361     // Converts from 100ns units in 1601 epoch to unix epoch in sec
362     return naNum((t * 1e-7) - 11644473600.0);
363 #else
364     time_t t;
365     struct timeval td;
366     do { t = time(0); gettimeofday(&td, 0); } while(t != time(0));
367     return naNum(t + 1e-6 * td.tv_usec);
368 #endif
369
370 }
371
372 // Table of extension functions.  Terminate with zeros.
373 static struct { char* name; naCFunction func; } funcs[] = {
374     { "getprop",   f_getprop },
375     { "setprop",   f_setprop },
376     { "print",     f_print },
377     { "_fgcommand", f_fgcommand },
378     { "settimer",  f_settimer },
379     { "_setlistener", f_setlistener },
380     { "removelistener", f_removelistener },
381     { "_cmdarg",  f_cmdarg },
382     { "_interpolate",  f_interpolate },
383     { "rand",  f_rand },
384     { "srand",  f_srand },
385     { "directory", f_directory },
386     { "systime", f_systime },
387     { 0, 0 }
388 };
389
390 naRef FGNasalSys::cmdArgGhost()
391 {
392     return propNodeGhost(_cmdArg);
393 }
394
395 void FGNasalSys::init()
396 {
397     int i;
398
399     _context = naNewContext();
400
401     // Start with globals.  Add it to itself as a recursive
402     // sub-reference under the name "globals".  This gives client-code
403     // write access to the namespace if someone wants to do something
404     // fancy.
405     _globals = naInit_std(_context);
406     naSave(_context, _globals);
407     hashset(_globals, "globals", _globals);
408
409     hashset(_globals, "math", naInit_math(_context));
410     hashset(_globals, "bits", naInit_bits(_context));
411     hashset(_globals, "io", naInit_io(_context));
412     hashset(_globals, "thread", naInit_thread(_context));
413     hashset(_globals, "utf8", naInit_utf8(_context));
414
415     // Add our custom extension functions:
416     for(i=0; funcs[i].name; i++)
417         hashset(_globals, funcs[i].name,
418                 naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
419
420     // And our SGPropertyNode wrapper
421     hashset(_globals, "props", genPropsModule());
422
423     // Make a "__gcsave" hash to hold the naRef objects which get
424     // passed to handles outside the interpreter (to protect them from
425     // begin garbage-collected).
426     _gcHash = naNewHash(_context);
427     hashset(_globals, "__gcsave", _gcHash);
428
429     // Now load the various source files in the Nasal directory
430     SGPath p(globals->get_fg_root());
431     p.append("Nasal");
432     ulDirEnt* dent;
433     ulDir* dir = ulOpenDir(p.c_str());
434     while(dir && (dent = ulReadDir(dir)) != 0) {
435         SGPath fullpath(p);
436         fullpath.append(dent->d_name);
437         SGPath file(dent->d_name);
438         if(file.extension() != "nas") continue;
439         loadModule(fullpath, file.base().c_str());
440     }
441     ulCloseDir(dir);
442
443     // set signal and remove node to avoid restoring at reinit
444     const char *s = "nasal-dir-initialized";
445     SGPropertyNode *signal = fgGetNode("/sim/signals", true);
446     signal->setBoolValue(s, true);
447     signal->removeChildren(s);
448
449     // Pull scripts out of the property tree, too
450     loadPropertyScripts();
451 }
452
453 void FGNasalSys::update(double)
454 {
455     if(_purgeListeners) {
456         _purgeListeners = false;
457         map<int, FGNasalListener *>::iterator it;
458         for(it = _listener.begin(); it != _listener.end();) {
459             FGNasalListener *nl = it->second;
460             if(nl->_dead) {
461                 _listener.erase(it++);
462                 delete nl;
463             } else {
464                 ++it;
465             }
466         }
467     }
468 }
469
470 // Loads the scripts found under /nasal in the global tree
471 void FGNasalSys::loadPropertyScripts()
472 {
473     SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
474     if(!nasal) return;
475
476     for(int i=0; i<nasal->nChildren(); i++) {
477         SGPropertyNode* n = nasal->getChild(i);
478
479         const char* module = n->getName();
480         if(n->hasChild("module"))
481             module = n->getStringValue("module");
482
483         // allow multiple files to be specified within in a single
484         // Nasal module tag
485         int j = 0;
486         SGPropertyNode *fn;
487         bool file_specified = false;
488         while ( (fn = n->getChild("file", j)) != NULL ) {
489             file_specified = true;
490             const char* file = fn->getStringValue();
491             SGPath p(globals->get_fg_root());
492             p.append(file);
493             loadModule(p, module);
494             j++;
495         }
496
497         // Old code which only allowed a single file to be specified per module
498         /*
499         const char* file = n->getStringValue("file");
500         if(!n->hasChild("file")) file = 0; // Hrm...
501         if(file) {
502             SGPath p(globals->get_fg_root());
503             p.append(file);
504             loadModule(p, module);
505         }
506         */
507         
508         const char* src = n->getStringValue("script");
509         if(!n->hasChild("script")) src = 0; // Hrm...
510         if(src)
511             createModule(module, n->getPath(), src, strlen(src));
512
513         if(!file_specified && !src)
514             SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
515                    "no <file> or <script> defined in " <<
516                    "/nasal/" << module);
517     }
518 }
519
520 // Logs a runtime error, with stack trace, to the FlightGear log stream
521 void FGNasalSys::logError(naContext context)
522 {
523     SG_LOG(SG_NASAL, SG_ALERT,
524            "Nasal runtime error: " << naGetError(context));
525     SG_LOG(SG_NASAL, SG_ALERT,
526            "  at " << naStr_data(naGetSourceFile(context, 0)) <<
527            ", line " << naGetLine(context, 0));
528     for(int i=1; i<naStackDepth(context); i++)
529         SG_LOG(SG_NASAL, SG_ALERT,
530                "  called from: " << naStr_data(naGetSourceFile(context, i)) <<
531                ", line " << naGetLine(context, i));
532 }
533
534 // Reads a script file, executes it, and places the resulting
535 // namespace into the global namespace under the specified module
536 // name.
537 void FGNasalSys::loadModule(SGPath file, const char* module)
538 {
539     int len = 0;
540     char* buf = readfile(file.c_str(), &len);
541     if(!buf) {
542         SG_LOG(SG_NASAL, SG_ALERT,
543                "Nasal error: could not read script file " << file.c_str()
544                << " into module " << module);
545         return;
546     }
547
548     createModule(module, file.c_str(), buf, len);
549     delete[] buf;
550 }
551
552 // Parse and run.  Save the local variables namespace, as it will
553 // become a sub-object of globals.  The optional "arg" argument can be
554 // used to pass an associated property node to the module, which can then
555 // be accessed via cmdarg().  (This is, for example, used by XML dialogs.)
556 void FGNasalSys::createModule(const char* moduleName, const char* fileName,
557                               const char* src, int len, const SGPropertyNode* arg)
558 {
559     naRef code = parse(fileName, src, len);
560     if(naIsNil(code))
561         return;
562
563     // See if we already have a module hash to use.  This allows the
564     // user to, for example, add functions to the built-in math
565     // module.  Make a new one if necessary.
566     naRef locals;
567     naRef modname = naNewString(_context);
568     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
569     if(!naHash_get(_globals, modname, &locals))
570         locals = naNewHash(_context);
571
572     _cmdArg = (SGPropertyNode*)arg;
573
574     call(code, locals);
575     hashset(_globals, moduleName, locals);
576 }
577
578 void FGNasalSys::deleteModule(const char* moduleName)
579 {
580     naRef modname = naNewString(_context);
581     naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
582     naHash_delete(_globals, modname);
583 }
584
585 naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
586 {
587     int errLine = -1;
588     naRef srcfile = naNewString(_context);
589     naStr_fromdata(srcfile, (char*)filename, strlen(filename));
590     naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
591     if(naIsNil(code)) {
592         SG_LOG(SG_NASAL, SG_ALERT,
593                "Nasal parse error: " << naGetError(_context) <<
594                " in "<< filename <<", line " << errLine);
595         return naNil();
596     }
597
598     // Bind to the global namespace before returning
599     return naBindFunction(_context, code, _globals);
600 }
601
602 bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
603 {
604     const char* nasal = arg->getStringValue("script");
605     const char* moduleName = arg->getStringValue("module");
606     naRef code = parse(arg->getPath(true), nasal, strlen(nasal));
607     if(naIsNil(code)) return false;
608
609     // Commands can be run "in" a module.  Make sure that module
610     // exists, and set it up as the local variables hash for the
611     // command.
612     naRef locals = naNil();
613     if(moduleName[0]) {
614         naRef modname = naNewString(_context);
615         naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
616         if(!naHash_get(_globals, modname, &locals)) {
617             locals = naNewHash(_context);
618             naHash_set(_globals, modname, locals);
619         }
620     }
621
622     // Cache this command's argument for inspection via cmdarg().  For
623     // performance reasons, we won't bother with it if the invoked
624     // code doesn't need it.
625     _cmdArg = (SGPropertyNode*)arg;
626
627     call(code, locals);
628     return true;
629 }
630
631 // settimer(func, dt, simtime) extension function.  The first argument
632 // is a Nasal function to call, the second is a delta time (from now),
633 // in seconds.  The third, if present, is a boolean value indicating
634 // that "real world" time (rather than simulator time) is to be used.
635 //
636 // Implementation note: the FGTimer objects don't live inside the
637 // garbage collector, so the Nasal handler functions have to be
638 // "saved" somehow lest they be inadvertently cleaned.  In this case,
639 // they are inserted into a globals.__gcsave hash and removed on
640 // expiration.
641 void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
642 {
643     // Extract the handler, delta, and simtime arguments:
644     naRef handler = argc > 0 ? args[0] : naNil();
645     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
646         naRuntimeError(c, "settimer() with invalid function argument");
647         return;
648     }
649
650     naRef delta = argc > 1 ? args[1] : naNil();
651     if(naIsNil(delta)) {
652         naRuntimeError(c, "settimer() with invalid time argument");
653         return;
654     }
655
656     bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
657
658     // Generate and register a C++ timer handler
659     NasalTimer* t = new NasalTimer;
660     t->handler = handler;
661     t->gcKey = gcSave(handler);
662     t->nasal = this;
663
664     globals->get_event_mgr()->addEvent("NasalTimer",
665                                        t, &NasalTimer::timerExpired,
666                                        delta.num, simtime);
667 }
668
669 void FGNasalSys::handleTimer(NasalTimer* t)
670 {
671     call(t->handler, naNil());
672     gcRelease(t->gcKey);
673 }
674
675 int FGNasalSys::gcSave(naRef r)
676 {
677     int key = _nextGCKey++;
678     naHash_set(_gcHash, naNum(key), r);
679     return key;
680 }
681
682 void FGNasalSys::gcRelease(int key)
683 {
684     naHash_delete(_gcHash, naNum(key));
685 }
686
687 void FGNasalSys::NasalTimer::timerExpired()
688 {
689     nasal->handleTimer(this);
690     delete this;
691 }
692
693 int FGNasalSys::_listenerId = 0;
694
695 // setlistener(property, func, bool) extension function.  The first argument
696 // is either a ghost (SGPropertyNode_ptr*) or a string (global property
697 // path), the second is a Nasal function, the optional third one a bool.
698 // If the bool is true, then the listener is executed initially. The
699 // setlistener() function returns a unique id number, that can be used
700 // as argument to the removelistener() function.
701 naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
702 {
703     SGPropertyNode_ptr node;
704     naRef prop = argc > 0 ? args[0] : naNil();
705     if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
706     else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
707     else {
708         naRuntimeError(c, "setlistener() with invalid property argument");
709         return naNil();
710     }
711
712     if(node->isTied())
713         SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
714                 node->getPath());
715
716     naRef handler = argc > 1 ? args[1] : naNil();
717     if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
718         naRuntimeError(c, "setlistener() with invalid function argument");
719         return naNil();
720     }
721
722     bool initial = argc > 2 && naTrue(args[2]);
723
724     FGNasalListener *nl = new FGNasalListener(node, handler, this,
725             gcSave(handler), _listenerId);
726     node->addChangeListener(nl, initial);
727
728     _listener[_listenerId] = nl;
729     return naNum(_listenerId++);
730 }
731
732 // removelistener(int) extension function. The argument is the id of
733 // a listener as returned by the setlistener() function.
734 naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
735 {
736     naRef id = argc > 0 ? args[0] : naNil();
737     map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
738
739     if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
740         naRuntimeError(c, "removelistener() with invalid listener id");
741         return naNil();
742     }
743
744     FGNasalListener *nl = it->second;
745     if(nl->_active) {
746         nl->_dead = true;
747         _purgeListeners = true;
748         return naNum(-1);
749     }
750
751     _listener.erase(it);
752     delete nl;
753     return naNum(_listener.size());
754 }
755
756
757
758 // FGNasalListener class.
759
760 FGNasalListener::FGNasalListener(SGPropertyNode_ptr node, naRef handler,
761                                  FGNasalSys* nasal, int key, int id) :
762     _node(node),
763     _handler(handler),
764     _gcKey(key),
765     _id(id),
766     _nas(nasal),
767     _active(0),
768     _dead(false)
769 {
770 }
771
772 FGNasalListener::~FGNasalListener()
773 {
774     _node->removeChangeListener(this);
775     _nas->gcRelease(_gcKey);
776 }
777
778 void FGNasalListener::valueChanged(SGPropertyNode* node)
779 {
780     // drop recursive listener calls
781     if(_active || _dead)
782         return;
783
784     SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
785     _active++;
786     _nas->_cmdArg = node;
787     _nas->call(_handler, naNil());
788     _active--;
789 }
790
791
792
793
794 // FGNasalModelData class.  If sgLoad3DModel() is called with a pointer to
795 // such a class, then it lets modelLoaded() run the <load> script, and the
796 // destructor the <unload> script. The latter happens when the model branch
797 // is removed from the scene graph.
798
799 void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
800                                    osg::Node *)
801 {
802     SGPropertyNode *n = prop->getNode("nasal"), *load;
803     if(!n)
804         return;
805
806     load = n->getNode("load");
807     _unload = n->getNode("unload");
808     if(!load && !_unload)
809         return;
810
811     _module = path;
812     const char *s = load ? load->getStringValue() : "";
813     nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
814 }
815
816 FGNasalModelData::~FGNasalModelData()
817 {
818     if(_module.empty())
819         return;
820
821     if(!nasalSys) {
822         SG_LOG(SG_NASAL, SG_ALERT, "Trying to run an <unload> script "
823                 "without Nasal subsystem present.");
824         return;
825     }
826
827     if(_unload) {
828         const char *s = _unload->getStringValue();
829         nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
830     }
831     nasalSys->deleteModule(_module.c_str());
832 }
833
834