]> git.mxchange.org Git - flightgear.git/commitdiff
Andy Ross:
authorcurt <curt>
Tue, 25 Nov 2003 21:08:36 +0000 (21:08 +0000)
committercurt <curt>
Tue, 25 Nov 2003 21:08:36 +0000 (21:08 +0000)
A set of changes to impliment Nasal support in FlightGear.  The nasal
interpreter is now part of SimGear.

configure.ac
src/Cockpit/radiostack.cxx
src/Main/Makefile.am
src/Main/fg_commands.cxx
src/Main/fg_init.cxx
src/Main/globals.cxx
src/Main/globals.hxx
src/Objects/Makefile.am
src/Scripting/Makefile.am
src/Scripting/NasalSys.cxx [new file with mode: 0644]
src/Scripting/NasalSys.hxx [new file with mode: 0644]

index e89dd2dc8c3b6bcabb1bd698bc27954acb642c96..aa39e3591f3d5c950e40bcad0aaaa87ed2ffe4f8 100644 (file)
@@ -354,29 +354,6 @@ dnl joystick lib
 AC_DEFINE([ENABLE_PLIB_JOYSTICK], 1, [Define to enable plib joystick support])
 
 
-dnl Checking for PSL in plib
-dnl version of plib, so check for it.
-AC_MSG_CHECKING([for plib PSL scripting support])
-AC_COMPILE_IFELSE([
-#include <plib/psl.h>
-
-int main() {
-  // not for running...
-  pslProgram program((pslExtension *)0);
-  program.compile("int main () {}", "");
-  return 0;
-}
-],
-  [AC_MSG_RESULT(yes);
-   have_plib_psl=yes],
-  AC_MSG_RESULT(no)
-)
-AM_CONDITIONAL(HAVE_PLIB_PSL, test "x$have_plib_psl" = "xyes")
-if test "x$have_plib_psl" = "xyes"; then
-   AC_DEFINE([HAVE_PLIB_PSL], 1,
-             [Define if plib version is new enough to provide "PSL"])
-fi
-
 dnl Check for the presence of SimGear
 AC_CHECK_HEADER(simgear/version.h)
 if test "x$ac_cv_header_simgear_version_h" != "xyes"; then
@@ -555,16 +532,6 @@ echo "================="
 
 echo "Prefix: $prefix"
 
-if test "x$have_plib_psl" = "xyes"; then
-    echo "Plib PSL scripting: yes"
-else
-    echo "Plib PSL scripting: $fg_psl_string_compile"
-    echo "  You will not be able to run scripts written in PSL"
-    echo "  and some advanced GUI and input features may not"
-    echo "  work.  Download and install the latest CVS version"
-    echo "  of plib if you would like to enable PSL support."
-fi
-
 if test "x$with_logging" != "x"; then
     echo "Debug messages: $with_logging"
 else
index 9af2567dc713d44008b4a14e822a723d77be4a86..4a6d38da58f44bffff14f5bdc27f0a378501809c 100644 (file)
@@ -77,8 +77,8 @@ FGRadioStack::init ()
     update(0);                 // FIXME: use dt
 
     // Search radio database once per second
-    globals->get_event_mgr()->add( "fgRadioSearch()", current_radiostack,
-                                   &FGRadioStack::search, 1000 );
+    globals->get_event_mgr()->addTask( "fgRadioSearch()", current_radiostack,
+                                       &FGRadioStack::search, 1 );
 }
 
 
index 7230ff957c692dfba864de94a2ca60d0228ab1b6..a14582f9bfa2cf7548ea160579d849ef94bae3cf 100644 (file)
@@ -22,14 +22,6 @@ else
 CLOUD3D_LIBS = 
 endif
 
-if HAVE_PLIB_PSL
-SCRIPTING_LIBS = $(top_builddir)/src/Scripting/libScripting.a
-PSL_LIBS = -lplibpsl
-else
-SCRIPTING_LIBS =
-PSL_LIBS =
-endif
-
 AM_CXXFLAGS = -DPKGLIBDIR=\"$(pkglibdir)\"
 
 EXTRA_DIST = 3dfx.sh runfgfs.in runfgfs.bat.in
@@ -82,7 +74,7 @@ fgfs_LDADD = \
        $(top_builddir)/src/Network/libNetwork.a \
        $(top_builddir)/src/Navaids/libNavaids.a \
        $(top_builddir)/src/Scenery/libScenery.a \
-        $(SCRIPTING_LIBS) \
+       $(top_builddir)/src/Scripting/libScripting.a \
        $(top_builddir)/src/Sound/libSound.a \
        $(top_builddir)/src/Airports/libAirports.a \
        $(MPLAYER_LIBS) \
@@ -94,11 +86,10 @@ fgfs_LDADD = \
        $(CLOUD3D_LIBS) \
        -lsgroute -lsgsky -lsgsound -lsgephem -lsgmaterial -lsgtgdb -lsgmodel \
        -lsgtiming -lsgio -lsgscreen -lsgmath -lsgbucket -lsgprops -lsgdebug \
-       -lsgmagvar -lsgmisc -lsgxml -lsgsound -lsgserial -lsgstructure \
+       -lsgmagvar -lsgmisc -lnasal -lsgxml -lsgsound -lsgserial -lsgstructure \
        $(THREAD_LIBS) \
        -lplibpu -lplibfnt -lplibjs -lplibnet -lplibssg -lplibsg -lplibul \
        $(network_LIBS) \
-        $(PSL_LIBS) \
        -lz \
        $(opengl_LIBS) \
        $(audio_LIBS)
index ff079276e6d134c2e8ac88d9e7b2446fc4761b75..9663ad8922752a5eaf130a22278da4385eae61b6 100644 (file)
@@ -26,6 +26,7 @@
 #if defined(HAVE_PLIB_PSL)
 #  include <Scripting/scriptmgr.hxx>
 #endif
+#include <Scripting/NasalSys.hxx>
 #include <Time/sunsolver.hxx>
 #include <Time/tmp.hxx>
 
@@ -170,6 +171,14 @@ do_script (const SGPropertyNode * arg)
 }
 #endif // HAVE_PLIB_PSL
 
+/**
+ * Built-in command: run a Nasal script.
+ */
+static bool
+do_nasal (const SGPropertyNode * arg)
+{
+    return ((FGNasalSys*)globals->get_subsystem("nasal"))->handleCommand(arg);
+}
 
 /**
  * Built-in command: exit FlightGear.
@@ -837,9 +846,8 @@ do_property_randomize (const SGPropertyNode * arg)
 
 
 /**
- * Built-in command: Show an XML-configured dialog.
- *
- * dialog-name: the name of the GUI dialog to display.
+ * Built-in command: reinit the data logging system based on the
+ * current contents of the /logger tree.
  */
 static bool
 do_data_logging_commit (const SGPropertyNode * arg)
@@ -1034,6 +1042,7 @@ static struct {
 #if defined(HAVE_PLIB_PSL)
     { "script", do_script },
 #endif // HAVE_PLIB_PSL
+    { "nasal", do_nasal },
     { "exit", do_exit },
     { "reinit", do_reinit },
     { "suspend", do_reinit },
index 7062ca8c62330801f4c81963ad6a4910be687320..5be3d3bce01990309626fe199f474f1c5251ffbf 100644 (file)
 #if defined(HAVE_PLIB_PSL)
 #include <Scripting/scriptmgr.hxx>
 #endif
+#include <Scripting/NasalSys.hxx>
 #include <Sound/fg_fx.hxx>
 #include <Systems/system_mgr.hxx>
 #include <Time/light.hxx>
@@ -1447,14 +1448,8 @@ bool fgInitSubsystems() {
     // Initialize the event manager subsystem.
     ////////////////////////////////////////////////////////////////////
 
-     globals->get_event_mgr()->bind();
      globals->get_event_mgr()->init();
-
-     // Output event stats every 60 seconds
-     globals->get_event_mgr()->add( "SGEventMgr::print_stats()",
-                                    globals->get_event_mgr(),
-                                    &SGEventMgr::print_stats,
-                                    60000 );
+     globals->get_event_mgr()->setFreezeProperty(fgGetNode("/sim/freeze/clock"));
 
     ////////////////////////////////////////////////////////////////////
     // Initialize the material property subsystem.
@@ -1483,10 +1478,10 @@ bool fgInitSubsystems() {
     }
 
     // cause refresh of viewer scenery timestamps every 15 seconds...
-    globals->get_event_mgr()->add( "FGTileMgr::refresh_view_timestamps()",
-                                   globals->get_tile_mgr(),
-                                   &FGTileMgr::refresh_view_timestamps,
-                                   15000 );
+    globals->get_event_mgr()->addTask( "FGTileMgr::refresh_view_timestamps()",
+                                       globals->get_tile_mgr(),
+                                       &FGTileMgr::refresh_view_timestamps,
+                                       15 );
 
     SG_LOG( SG_GENERAL, SG_DEBUG,
             "Current terrain elevation after tile mgr init " <<
@@ -1539,8 +1534,8 @@ bool fgInitSubsystems() {
     ////////////////////////////////////////////////////////////////////
 
     // update the current timezone each 30 minutes
-    globals->get_event_mgr()->add( "fgUpdateLocalTime()",
-                                   &fgUpdateLocalTime, 30*60*1000 );
+    globals->get_event_mgr()->addTask( "fgUpdateLocalTime()",
+                                       &fgUpdateLocalTime, 30*60 );
 
 
     ////////////////////////////////////////////////////////////////////
@@ -1788,6 +1783,14 @@ bool fgInitSubsystems() {
     globals->get_multiplayer_rx_mgr()->init();
 #endif
 
+    ////////////////////////////////////////////////////////////////////////
+    // Initialize the Nasal interpreter.
+    // Do this last, so that the loaded scripts see initialized state
+    ////////////////////////////////////////////////////////////////////////
+    FGNasalSys* nasal = new FGNasalSys();
+    globals->add_subsystem("nasal", nasal);
+    nasal->init();
+
     ////////////////////////////////////////////////////////////////////////
     // End of subsystem initialization.
     ////////////////////////////////////////////////////////////////////
index e6a310535de5b01e5dbe9951be43e5ab7f0d4a22..204c298909de760100cbaad2029aaf9ca944940f 100644 (file)
@@ -134,26 +134,6 @@ FGGlobals::get_event_mgr () const
 }
 
 
-void
-FGGlobals::add_event (const char * name,
-                      int repeat_value,
-                      int initial_value)
-{
-    event_mgr->add(name, subsystem_mgr->get_subsystem(name),
-                   repeat_value, initial_value);
-}
-
-void
-FGGlobals::add_event (const char * name,
-                      SGSubsystem * subsystem,
-                      int repeat_value,
-                      int initial_value)
-{
-    event_mgr->add(name, subsystem, repeat_value, initial_value);
-}
-
-
-
 // Save the current state as the initial state.
 void
 FGGlobals::saveInitialState ()
index 67dbf19fdfb0b27d8f1a3247a802b607387e5d8c..9e91b443bce4b8b780d591748bfce7a163ec4016 100644 (file)
@@ -215,35 +215,6 @@ public:
 
     virtual SGEventMgr * get_event_mgr () const;
 
-    virtual void add_event (const char * name,
-                            int repeat_value,
-                            int initial_value = -1 );
-
-    virtual void add_event (const char * name,
-                            SGSubsystem * subsystem,
-                            int repeat_value,
-                            int initial_value = -1 );
-
-    template< typename Fun >
-    inline void add_event( const char * name,
-                           const Fun& func,
-                           SGEvent::interval_type repeat_value,
-                           SGEvent::interval_type initial_value = -1 )
-    {
-        event_mgr->add( name, get_subsystem( name ), func,
-                        repeat_value, initial_value);
-    }
-
-    template< typename Fun >
-    inline void add_event( const char * name,
-                           SGSubsystem * subsystem,
-                           const Fun& func,
-                           SGEvent::interval_type repeat_value,
-                           SGEvent::interval_type initial_value = -1 )
-    {
-        event_mgr->add( name, subsystem, func, repeat_value, initial_value);
-    }
-
     inline double get_sim_time_sec () const { return sim_time_sec; }
     inline void inc_sim_time_sec (double dt) { sim_time_sec += dt; }
     inline void set_sim_time_sec (double t) { sim_time_sec = t; }
index ceb669c3710b0fd124779a6989c2898d52a9160c..0872d6f6ba5cf8a83ec152dd8be4dd78c5de8263 100644 (file)
@@ -1,6 +1,7 @@
 noinst_LIBRARIES = libObjects.a
 
-libObjects_a_SOURCES = \
-       ssgEntityArray.hxx ssgEntityArray.cxx
+libObjects_a_SOURCES = 
+
+#      ssgEntityArray.hxx ssgEntityArray.cxx
 
 INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
index 1d568bb8ed786b958a721a256ab28c2e77300ee4..4eb53621fdc069c8a8b22ceed10369935d39d17d 100644 (file)
@@ -1,5 +1,6 @@
 noinst_LIBRARIES = libScripting.a
 
-libScripting_a_SOURCES = scriptmgr.cxx scriptmgr.hxx
+libScripting_a_SOURCES = NasalSys.cxx NasalSys.hxx
+# libScripting_a_SOURCES = scriptmgr.cxx scriptmgr.hxx NasalSys.cxx NasalSys.hxx
 
 INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx
new file mode 100644 (file)
index 0000000..e481ac5
--- /dev/null
@@ -0,0 +1,383 @@
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <plib/ul.h>
+
+#include <simgear/nasal/nasal.h>
+#include <simgear/props/props.hxx>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/structure/commands.hxx>
+
+#include <Main/globals.hxx>
+
+#include "NasalSys.hxx"
+
+// Read and return file contents in a single buffer.  Note use of
+// stat() to get the file size.  This is a win32 function, believe it
+// or not. :) Note the REALLY IMPORTANT use of the "b" flag to fopen.
+// Text mode brain damage will kill us if we're trying to do bytewise
+// I/O.
+static char* readfile(const char* file, int* lenOut)
+{
+    struct stat data;
+    if(stat(file, &data) != 0) return 0;
+    FILE* f = fopen(file, "rb");
+    if(!f) return 0;
+    char* buf = new char[data.st_size];
+    *lenOut = fread(buf, 1, data.st_size, f);
+    fclose(f);
+    if(*lenOut != data.st_size) {
+        // Shouldn't happen, but warn anyway since it represents a
+        // platform bug and not a typical runtime error (missing file,
+        // etc...)
+        SG_LOG(SG_NASAL, SG_ALERT,
+               "ERROR in Nasal initialization: " <<
+               "short count returned from fread().  Check your C library!");
+        delete[] buf;
+        return 0;
+    }
+    return buf;
+}
+
+FGNasalSys::FGNasalSys()
+{
+    _context = 0;
+    _globals = naNil();
+    _timerHash = naNil();
+    _nextTimerHashKey = 0; // Any value will do
+}
+
+FGNasalSys::~FGNasalSys()
+{
+    // Nasal doesn't have a "destroy context" API yet. :(
+    // Not a problem for a global subsystem that will never be
+    // destroyed.  And the context is actually a global, so no memory
+    // is technically leaked (although the GC pool memory obviously
+    // won't be freed).
+    _context = 0;
+    _globals = naNil();
+}
+
+// Utility.  Sets a named key in a hash by C string, rather than nasal
+// string object.
+void FGNasalSys::hashset(naRef hash, const char* key, naRef val)
+{
+    naRef s = naNewString(_context);
+    naStr_fromdata(s, (char*)key, strlen(key));
+    naHash_set(hash, s, val);
+}
+
+// The get/setprop functions accept a *list* of strings and walk
+// through the property tree with them to find the appropriate node.
+// This allows a Nasal object to hold onto a property path and use it
+// like a node object, e.g. setprop(ObjRoot, "size-parsecs", 2.02).  This
+// is the utility function that walks the property tree.
+// Future enhancement: support integer arguments to specify array
+// elements.
+static SGPropertyNode* findnode(naContext c, naRef vec, int len)
+{
+    SGPropertyNode* p = globals->get_props();
+    for(int i=0; i<len; i++) {
+        naRef a = naVec_get(vec, i);
+        if(!naIsString(a)) return 0;
+        p = p->getNode(naStr_data(a));
+        if(p == 0) return 0;
+    }
+    return p;
+}
+
+// getprop() extension function.  Concatenates its string arguments as
+// property names and returns the value of the specified property.  Or
+// nil if it doesn't exist.
+static naRef f_getprop(naContext c, naRef args)
+{
+    const SGPropertyNode* p = findnode(c, args, naVec_size(args));
+    if(!p) return naNil();
+
+    switch(p->getType()) {
+    case SGPropertyNode::BOOL:   case SGPropertyNode::INT:
+    case SGPropertyNode::LONG:   case SGPropertyNode::FLOAT:
+    case SGPropertyNode::DOUBLE:
+        return naNum(p->getDoubleValue());
+
+    case SGPropertyNode::STRING:
+        {
+            naRef nastr = naNewString(c);
+            const char* val = p->getStringValue();
+            naStr_fromdata(nastr, (char*)val, strlen(val));
+            return nastr;
+        }
+    default:
+        return naNil();
+    }
+}
+
+// setprop() extension function.  Concatenates its string arguments as
+// property names and sets the value of the specified property to the
+// final argument.
+static naRef f_setprop(naContext c, naRef args)
+{
+#define BUFLEN 1024
+    int argc = naVec_size(args);
+    char buf[BUFLEN + 1];
+    buf[BUFLEN] = 0;
+    char* p = buf;
+    int buflen = BUFLEN;
+    for(int i=0; i<argc-1; i++) {
+        naRef s = naStringValue(c, naVec_get(args, i));
+        if(naIsNil(s)) return naNil();
+        strncpy(p, naStr_data(s), buflen);
+        p += naStr_len(s);
+        buflen = BUFLEN - (p - buf);
+        if(i < (argc-2) && buflen > 0) {
+            *p++ = '/';
+            buflen--;
+        }
+    }
+
+    SGPropertyNode* props = globals->get_props();
+    naRef val = naVec_get(args, argc-1);
+    if(naIsString(val)) props->setStringValue(buf, naStr_data(val));
+    else                props->setDoubleValue(buf, naNumValue(val).num);
+    return naNil();
+#undef BUFLEN
+}
+
+// print() extension function.  Concatenates and prints its arguments
+// to the FlightGear log.  Uses the highest log level (SG_ALERT), to
+// make sure it appears.  Is there better way to do this?
+static naRef f_print(naContext c, naRef args)
+{
+#define BUFLEN 1024
+    char buf[BUFLEN + 1];
+    buf[BUFLEN] = 0; // extra nul to handle strncpy brain damage
+    char* p = buf;
+    int buflen = BUFLEN;
+    int n = naVec_size(args);
+    for(int i=0; i<n; i++) {
+        naRef s = naStringValue(c, naVec_get(args, i));
+        if(naIsNil(s)) continue;
+        strncpy(p, naStr_data(s), buflen);
+        p += naStr_len(s);
+        buflen = BUFLEN - (p - buf);
+        if(buflen <= 0) break;
+    }
+    SG_LOG(SG_GENERAL, SG_ALERT, buf);
+    return naNil();
+#undef BUFLEN
+}
+
+// fgcommand() extension function.  Executes a named command via the
+// FlightGear command manager.  Takes a single property node name as
+// an argument.
+static naRef f_fgcommand(naContext c, naRef args)
+{
+    naRef cmd = naVec_get(args, 0);
+    naRef props = naVec_get(args, 1);
+    if(!naIsString(cmd) || !naIsString(props)) return naNil();
+
+    SGPropertyNode* pnode =
+        globals->get_props()->getNode(naStr_data(props));
+    if(pnode)
+        globals->get_commands()->execute(naStr_data(cmd), pnode);
+    return naNil();
+}
+
+// settimer(func, dt, simtime) extension function.  Falls through to
+// FGNasalSys::setTimer().  See there for docs.
+static naRef f_settimer(naContext c, naRef args)
+{
+    FGNasalSys* nasal = (FGNasalSys*)globals->get_subsystem("nasal");
+    nasal->setTimer(args);
+    return naNil();
+}
+
+// Table of extension functions.  Terminate with zeros.
+static struct { char* name; naCFunction func; } funcs[] = {
+    { "getprop",   f_getprop },
+    { "setprop",   f_setprop },
+    { "print",     f_print },
+    { "fgcommand", f_fgcommand },
+    { "settimer",  f_settimer },
+    { 0, 0 }
+};
+
+void FGNasalSys::init()
+{
+    _context = naNewContext();
+
+    // Start with globals.  Add it to itself as a recursive
+    // sub-reference under the name "globals".  This gives client-code
+    // write access to the namespace if someone wants to do something
+    // fancy.
+    _globals = naStdLib(_context);
+    naSave(_context, _globals);
+    hashset(_globals, "globals", _globals);
+
+    // Add in the math library under "math"
+    hashset(_globals, "math", naMathLib(_context));
+
+    // Add our custom extension functions:
+    for(int i=0; funcs[i].name; i++)
+        hashset(_globals, funcs[i].name,
+                naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
+
+    // Make a "__timers" hash to hold the settimer() handlers (to
+    // protect them from begin garbage-collected).
+    _timerHash = naNewHash(_context);
+    hashset(_globals, "__timers", _timerHash);
+
+    // Now load the various source files in the Nasal directory
+    SGPath p(globals->get_fg_root());
+    p.append("Nasal");
+    ulDirEnt* dent;
+    ulDir* dir = ulOpenDir(p.c_str());
+    while(dir && (dent = ulReadDir(dir)) != 0) {
+        SGPath fullpath(p);
+        fullpath.append(dent->d_name);
+        SGPath file(dent->d_name);
+        if(file.extension() != "nas") continue;
+        readScriptFile(fullpath, file.base().c_str());
+    }
+}
+
+// Logs a runtime error, with stack trace, to the FlightGear log stream
+void FGNasalSys::logError()
+{
+    SG_LOG(SG_NASAL, SG_ALERT,
+           "Nasal runtime error: " << naGetError(_context));
+    SG_LOG(SG_NASAL, SG_ALERT,
+           "  at " << naStr_data(naGetSourceFile(_context, 0)) <<
+           ", line " << naGetLine(_context, 0));
+    for(int i=1; i<naStackDepth(_context); i++)
+        SG_LOG(SG_NASAL, SG_ALERT,
+               "  called from: " << naStr_data(naGetSourceFile(_context, i)) <<
+               ", line " << naGetLine(_context, i));
+}
+
+// Reads a script file, executes it, and places the resulting
+// namespace into the global namespace under the specified module
+// name.
+void FGNasalSys::readScriptFile(SGPath file, const char* lib)
+{
+    int len = 0;
+    char* buf = readfile(file.c_str(), &len);
+    if(!buf) return;
+
+    // Parse and run.  Save the local variables namespace, as it will
+    // become a sub-object of globals.
+    naRef code = parse(file.c_str(), buf, len);
+    delete[] buf;
+    if(naIsNil(code))
+        return;
+
+    naRef locals = naNewHash(_context);
+    naCall(_context, code, naNil(), naNil(), locals);
+    if(naGetError(_context)) {
+        logError();
+        return;
+    }
+
+    hashset(_globals, lib, locals);
+}
+
+naRef FGNasalSys::parse(const char* filename, const char* buf, int len)
+{
+    int errLine = -1;
+    naRef srcfile = naNewString(_context);
+    naStr_fromdata(srcfile, (char*)filename, strlen(filename));
+    naRef code = naParseCode(_context, srcfile, 1, (char*)buf, len, &errLine);
+    if(naIsNil(code)) {
+        SG_LOG(SG_NASAL, SG_ALERT,
+               "Nasal parse error: " << naGetError(_context) <<
+               " in "<< filename <<", line " << errLine);
+        return naNil();
+    }
+
+    // Bind to the global namespace before returning
+    return naBindFunction(_context, code, _globals);
+}
+
+bool FGNasalSys::handleCommand(const SGPropertyNode* arg)
+{
+    // Parse the Nasal source.  I'd love to use the property name of
+    // the argument, but it's actually a *clone* of the original
+    // location in the property tree.  arg->getPath() returns an empty
+    // string.
+    const char* nasal = arg->getStringValue("script");
+    naRef code = parse("<command>", nasal, strlen(nasal));
+    if(naIsNil(code)) return false;
+    
+    // FIXME: Cache the just-created code object somewhere, but watch
+    // for changes to the source in the property tree.  Maybe store an
+    // integer index into a Nasal vector in the original property
+    // location?
+
+    // Extract the "value" or "offset" arguments if present
+    naRef locals = naNil();
+    if(arg->hasValue("value")) {
+        locals = naNewHash(_context);
+        hashset(locals, "value", naNum(arg->getDoubleValue("value")));
+    } else if(arg->hasValue("offset")) {
+        locals = naNewHash(_context);
+        hashset(locals, "offset", naNum(arg->getDoubleValue("offset")));
+    }
+
+    // Call it!
+    naRef result = naCall(_context, code, naNil(), naNil(), locals);
+    if(!naGetError(_context)) return true;
+    logError();
+    return false;
+}
+
+// settimer(func, dt, simtime) extension function.  The first argument
+// is a Nasal function to call, the second is a delta time (from now),
+// in seconds.  The third, if present, is a boolean value indicating
+// that "simulator" time (rather than real time) is to be used.
+//
+// Implementation note: the FGTimer objects don't live inside the
+// garbage collector, so the Nasal handler functions have to be
+// "saved" somehow lest they be inadvertently cleaned.  In this case,
+// they are inserted into a globals._timers hash and removed on
+// expiration.
+void FGNasalSys::setTimer(naRef args)
+{
+    // Extract the handler, delta, and simtime arguments:
+    naRef handler = naVec_get(args, 0);
+    if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler)))
+        return;
+
+    naRef delta = naNumValue(naVec_get(args, 1));
+    if(naIsNil(delta)) return;
+    
+    bool simtime = naTrue(naVec_get(args, 2)) ? true : false;
+
+    // Generate and register a C++ timer handler
+    NasalTimer* t = new NasalTimer;
+    t->handler = handler;
+    t->hashKey = _nextTimerHashKey++;
+    t->nasal = this;
+
+    globals->get_event_mgr()->addEvent("NasalTimer",
+                                       t, &NasalTimer::timerExpired,
+                                       delta.num, simtime);
+
+
+    // Save the handler in the globals.__timers hash to prevent
+    // garbage collection.
+    naHash_set(_timerHash, naNum(t->hashKey), handler);
+}
+
+void FGNasalSys::handleTimer(NasalTimer* t)
+{
+    naCall(_context, t->handler, naNil(), naNil(), naNil());
+    naHash_delete(_timerHash, naNum(t->hashKey));
+}
+
+void FGNasalSys::NasalTimer::timerExpired()
+{
+    nasal->handleTimer(this);
+    delete this;
+}
diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx
new file mode 100644 (file)
index 0000000..0de8fcd
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef __NASALSYS_HXX
+#define __NASALSYS_HXX
+
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/structure/subsystem_mgr.hxx>
+#include <simgear/nasal/nasal.h>
+
+class FGNasalSys : public SGSubsystem
+{
+public:
+    FGNasalSys();
+    virtual ~FGNasalSys();
+    virtual void init();
+    virtual void update(double dt) { /* noop */ }
+
+    virtual bool handleCommand(const SGPropertyNode* arg);
+
+    // Implementation of the settimer extension function
+    void setTimer(naRef args);
+
+private:
+    //
+    // FGTimer subclass for handling Nasal timer callbacks.
+    // See the implementation of the settimer() extension function for
+    // more notes.
+    //
+    struct NasalTimer {
+        virtual void timerExpired();
+        naRef handler;
+        int hashKey;
+        FGNasalSys* nasal;
+    };
+
+    void readScriptFile(SGPath file, const char* lib);
+    void hashset(naRef hash, const char* key, naRef val);
+    void logError();
+    naRef parse(const char* filename, const char* buf, int len);
+    void handleTimer(NasalTimer* t);
+
+    naContext _context;
+    naRef _globals;
+    naRef _timerHash;
+
+    int _nextTimerHashKey;
+};
+
+#endif // __NASALSYS_HXX