2006-02-18 13:58:09 +00:00
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2007-05-14 16:24:41 +00:00
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
|
|
# include <sys/time.h> // gettimeofday
|
|
|
|
#endif
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
#include <fstream>
|
2003-11-25 21:08:36 +00:00
|
|
|
|
|
|
|
#include <plib/ul.h>
|
|
|
|
|
|
|
|
#include <simgear/nasal/nasal.h>
|
|
|
|
#include <simgear/props/props.hxx>
|
2003-12-05 01:54:39 +00:00
|
|
|
#include <simgear/math/sg_random.h>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <simgear/misc/sg_path.hxx>
|
2003-12-05 01:54:39 +00:00
|
|
|
#include <simgear/misc/interpolator.hxx>
|
add geodinfo(<lat>, <lon>) function that returns an array [<elev>, <matdata>]
or nil if no terrain intersection could be made (tile not loaded yet).
<matdata> is a hash with information about the surface material, or nil
if no material is assigned (shouldn't really happen, but one never knows).
Example:
var ac = geo.aircraft_position();
var data = geoddata(ac.lat(), ac.lon());
debug.dump(data);
# which outputs
[ 294.5862574369132, { light_coverage : 0, bumpiness : 0, load_resistance : 1e+30,
solid : 1, names : [ "pc_taxiway", "dirt_rwytaxiway" ], friction_factor : 1,
rolling_friction : 0.02 } ]
With this information it can be determined how far an object would sink in,
if the coordinate is on a runway, etc.
foreach (var n; data[1].names)
if (string.match(n, "p[ac]_*"))
im_on_a_runway();
2007-06-23 15:25:41 +00:00
|
|
|
#include <simgear/scene/material/mat.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <simgear/structure/commands.hxx>
|
2007-06-16 18:22:20 +00:00
|
|
|
#include <simgear/math/sg_geodesy.hxx>
|
2008-07-31 12:04:32 +00:00
|
|
|
#include <simgear/structure/event_mgr.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2007-10-02 15:31:03 +00:00
|
|
|
#include <Airports/runways.hxx>
|
|
|
|
#include <Airports/simple.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
#include <Main/globals.hxx>
|
2003-12-05 01:54:39 +00:00
|
|
|
#include <Main/fg_props.hxx>
|
2008-07-22 20:26:17 +00:00
|
|
|
#include <Main/util.hxx>
|
add geodinfo(<lat>, <lon>) function that returns an array [<elev>, <matdata>]
or nil if no terrain intersection could be made (tile not loaded yet).
<matdata> is a hash with information about the surface material, or nil
if no material is assigned (shouldn't really happen, but one never knows).
Example:
var ac = geo.aircraft_position();
var data = geoddata(ac.lat(), ac.lon());
debug.dump(data);
# which outputs
[ 294.5862574369132, { light_coverage : 0, bumpiness : 0, load_resistance : 1e+30,
solid : 1, names : [ "pc_taxiway", "dirt_rwytaxiway" ], friction_factor : 1,
rolling_friction : 0.02 } ]
With this information it can be determined how far an object would sink in,
if the coordinate is on a runway, etc.
foreach (var n; data[1].names)
if (string.match(n, "p[ac]_*"))
im_on_a_runway();
2007-06-23 15:25:41 +00:00
|
|
|
#include <Scenery/scenery.hxx>
|
2003-11-25 21:08:36 +00:00
|
|
|
|
|
|
|
#include "NasalSys.hxx"
|
|
|
|
|
2007-05-01 17:03:50 +00:00
|
|
|
static FGNasalSys* nasalSys = 0;
|
|
|
|
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// 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: " <<
|
2003-12-01 14:35:49 +00:00
|
|
|
"short count returned from fread() of " << file <<
|
|
|
|
". Check your C library!");
|
2003-11-25 21:08:36 +00:00
|
|
|
delete[] buf;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGNasalSys::FGNasalSys()
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
nasalSys = this;
|
2003-11-25 21:08:36 +00:00
|
|
|
_context = 0;
|
|
|
|
_globals = naNil();
|
2003-12-05 01:54:39 +00:00
|
|
|
_gcHash = naNil();
|
|
|
|
_nextGCKey = 0; // Any value will do
|
2006-07-19 19:46:53 +00:00
|
|
|
_callCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does a naCall() in a new context. Wrapped here to make lock
|
|
|
|
// tracking easier. Extension functions are called with the lock, but
|
|
|
|
// we have to release it before making a new naCall(). So rather than
|
|
|
|
// drop the lock in every extension function that might call back into
|
|
|
|
// Nasal, we keep a stack depth counter here and only unlock/lock
|
|
|
|
// around the naCall if it isn't the first one.
|
2007-10-15 16:28:40 +00:00
|
|
|
naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals)
|
2006-07-19 19:46:53 +00:00
|
|
|
{
|
|
|
|
naContext ctx = naNewContext();
|
|
|
|
if(_callCount) naModUnlock();
|
|
|
|
_callCount++;
|
2007-10-15 16:28:40 +00:00
|
|
|
naRef result = naCall(ctx, code, argc, args, naNil(), locals);
|
2006-07-19 19:46:53 +00:00
|
|
|
if(naGetError(ctx))
|
|
|
|
logError(ctx);
|
|
|
|
_callCount--;
|
|
|
|
if(_callCount) naModLock();
|
|
|
|
naFreeContext(ctx);
|
|
|
|
return result;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FGNasalSys::~FGNasalSys()
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
nasalSys = 0;
|
2006-06-10 22:21:22 +00:00
|
|
|
map<int, FGNasalListener *>::iterator it, end = _listener.end();
|
2007-01-23 15:53:04 +00:00
|
|
|
for(it = _listener.begin(); it != end; ++it)
|
2006-06-10 22:21:22 +00:00
|
|
|
delete it->second;
|
|
|
|
|
2007-03-29 18:50:28 +00:00
|
|
|
naFreeContext(_context);
|
2003-11-25 21:08:36 +00:00
|
|
|
_globals = naNil();
|
|
|
|
}
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
bool FGNasalSys::parseAndRun(const char* sourceCode)
|
|
|
|
{
|
|
|
|
naRef code = parse("FGNasalSys::parseAndRun()", sourceCode,
|
|
|
|
strlen(sourceCode));
|
|
|
|
if(naIsNil(code))
|
|
|
|
return false;
|
2007-10-15 16:28:40 +00:00
|
|
|
call(code, 0, 0, naNil());
|
2006-07-19 19:46:53 +00:00
|
|
|
return true;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
2003-12-05 01:54:39 +00:00
|
|
|
FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name)
|
|
|
|
{
|
|
|
|
FGNasalScript* script = new FGNasalScript();
|
|
|
|
script->_gcKey = -1; // important, if we delete it on a parse
|
|
|
|
script->_nas = this; // error, don't clobber a real handle!
|
|
|
|
|
|
|
|
char buf[256];
|
|
|
|
if(!name) {
|
2007-05-17 18:43:36 +00:00
|
|
|
sprintf(buf, "FGNasalScript@%p", (void *)script);
|
2003-12-05 01:54:39 +00:00
|
|
|
name = buf;
|
|
|
|
}
|
|
|
|
|
2005-04-26 20:57:06 +00:00
|
|
|
script->_code = parse(name, src, strlen(src));
|
2003-12-05 01:54:39 +00:00
|
|
|
if(naIsNil(script->_code)) {
|
|
|
|
delete script;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
script->_gcKey = gcSave(script->_code);
|
|
|
|
return script;
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// 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.
|
2005-04-18 19:49:13 +00:00
|
|
|
static SGPropertyNode* findnode(naContext c, naRef* vec, int len)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode* p = globals->get_props();
|
2006-05-23 18:55:38 +00:00
|
|
|
try {
|
|
|
|
for(int i=0; i<len; i++) {
|
|
|
|
naRef a = vec[i];
|
|
|
|
if(!naIsString(a)) return 0;
|
|
|
|
p = p->getNode(naStr_data(a));
|
|
|
|
if(p == 0) return 0;
|
|
|
|
}
|
|
|
|
} catch (const string& err) {
|
|
|
|
naRuntimeError(c, (char *)err.c_str());
|
|
|
|
return 0;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
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.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_getprop(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2005-04-18 19:49:13 +00:00
|
|
|
const SGPropertyNode* p = findnode(c, args, argc);
|
2003-11-25 21:08:36 +00:00
|
|
|
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:
|
2004-03-24 19:06:54 +00:00
|
|
|
case SGPropertyNode::UNSPECIFIED:
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
naRef nastr = naNewString(c);
|
|
|
|
const char* val = p->getStringValue();
|
|
|
|
naStr_fromdata(nastr, (char*)val, strlen(val));
|
|
|
|
return nastr;
|
|
|
|
}
|
2004-03-24 19:06:54 +00:00
|
|
|
case SGPropertyNode::ALIAS: // <--- FIXME, recurse?
|
2003-11-25 21:08:36 +00:00
|
|
|
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.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_setprop(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
#define BUFLEN 1024
|
|
|
|
char buf[BUFLEN + 1];
|
|
|
|
buf[BUFLEN] = 0;
|
|
|
|
char* p = buf;
|
|
|
|
int buflen = BUFLEN;
|
|
|
|
for(int i=0; i<argc-1; i++) {
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef s = naStringValue(c, args[i]);
|
2003-11-25 21:08:36 +00:00
|
|
|
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();
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef val = args[argc-1];
|
2006-05-23 18:55:38 +00:00
|
|
|
try {
|
2007-10-11 07:58:56 +00:00
|
|
|
bool r;
|
|
|
|
if(naIsString(val)) r = props->setStringValue(buf, naStr_data(val));
|
2006-10-17 19:58:33 +00:00
|
|
|
else {
|
|
|
|
naRef n = naNumValue(val);
|
|
|
|
if(naIsNil(n))
|
|
|
|
naRuntimeError(c, "setprop() value is not string or number");
|
2007-10-11 07:58:56 +00:00
|
|
|
r = props->setDoubleValue(buf, n.num);
|
2006-10-17 19:58:33 +00:00
|
|
|
}
|
2007-10-11 07:58:56 +00:00
|
|
|
if(!r) naRuntimeError(c, "setprop(): property is not writable");
|
2006-05-23 18:55:38 +00:00
|
|
|
} catch (const string& err) {
|
|
|
|
naRuntimeError(c, (char *)err.c_str());
|
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
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?
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_print(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2007-01-28 12:16:37 +00:00
|
|
|
string buf;
|
2005-04-18 19:49:13 +00:00
|
|
|
int n = argc;
|
2003-11-25 21:08:36 +00:00
|
|
|
for(int i=0; i<n; i++) {
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef s = naStringValue(c, args[i]);
|
2003-11-25 21:08:36 +00:00
|
|
|
if(naIsNil(s)) continue;
|
2007-01-28 12:16:37 +00:00
|
|
|
buf += naStr_data(s);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, buf);
|
2007-01-28 12:16:37 +00:00
|
|
|
return naNum(buf.length());
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// fgcommand() extension function. Executes a named command via the
|
|
|
|
// FlightGear command manager. Takes a single property node name as
|
|
|
|
// an argument.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_fgcommand(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2007-06-07 16:17:48 +00:00
|
|
|
naRef cmd = argc > 0 ? args[0] : naNil();
|
|
|
|
naRef props = argc > 1 ? args[1] : naNil();
|
|
|
|
if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
|
2005-04-18 19:49:13 +00:00
|
|
|
naRuntimeError(c, "bad arguments to fgcommand()");
|
2007-06-07 16:57:59 +00:00
|
|
|
SGPropertyNode_ptr tmp, *node;
|
2007-06-07 16:17:48 +00:00
|
|
|
if(!naIsNil(props))
|
|
|
|
node = (SGPropertyNode_ptr*)naGhost_ptr(props);
|
2007-06-07 16:57:59 +00:00
|
|
|
else {
|
|
|
|
tmp = new SGPropertyNode();
|
|
|
|
node = &tmp;
|
|
|
|
}
|
2007-01-13 19:18:03 +00:00
|
|
|
return naNum(globals->get_commands()->execute(naStr_data(cmd), *node));
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// settimer(func, dt, simtime) extension function. Falls through to
|
|
|
|
// FGNasalSys::setTimer(). See there for docs.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_settimer(naContext c, naRef me, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
2007-05-12 18:15:45 +00:00
|
|
|
nasalSys->setTimer(c, argc, args);
|
2003-11-25 21:08:36 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
2006-02-28 14:55:37 +00:00
|
|
|
// setlistener(func, property, bool) extension function. Falls through to
|
2005-12-16 19:11:03 +00:00
|
|
|
// FGNasalSys::setListener(). See there for docs.
|
|
|
|
static naRef f_setlistener(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->setListener(c, argc, args);
|
2006-02-28 14:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// removelistener(int) extension function. Falls through to
|
|
|
|
// FGNasalSys::removeListener(). See there for docs.
|
|
|
|
static naRef f_removelistener(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->removeListener(c, argc, args);
|
2005-12-16 19:11:03 +00:00
|
|
|
}
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// Returns a ghost handle to the argument to the currently executing
|
|
|
|
// command
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_cmdarg(naContext c, naRef me, int argc, naRef* args)
|
2003-12-01 14:35:49 +00:00
|
|
|
{
|
2007-05-01 17:03:50 +00:00
|
|
|
return nasalSys->cmdArgGhost();
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
2003-12-05 01:54:39 +00:00
|
|
|
// Sets up a property interpolation. The first argument is either a
|
|
|
|
// ghost (SGPropertyNode_ptr*) or a string (global property path) to
|
|
|
|
// interpolate. The second argument is a vector of pairs of
|
|
|
|
// value/delta numbers.
|
2005-04-18 19:49:13 +00:00
|
|
|
static naRef f_interpolate(naContext c, naRef me, int argc, naRef* args)
|
2003-12-05 01:54:39 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode* node;
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef prop = argc > 0 ? args[0] : naNil();
|
2003-12-05 01:54:39 +00:00
|
|
|
if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
|
|
|
|
else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
|
|
|
|
else return naNil();
|
|
|
|
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef curve = argc > 1 ? args[1] : naNil();
|
2003-12-05 01:54:39 +00:00
|
|
|
if(!naIsVector(curve)) return naNil();
|
|
|
|
int nPoints = naVec_size(curve) / 2;
|
|
|
|
double* values = new double[nPoints];
|
|
|
|
double* deltas = new double[nPoints];
|
|
|
|
for(int i=0; i<nPoints; i++) {
|
|
|
|
values[i] = naNumValue(naVec_get(curve, 2*i)).num;
|
|
|
|
deltas[i] = naNumValue(naVec_get(curve, 2*i+1)).num;
|
|
|
|
}
|
|
|
|
|
2007-05-01 18:06:48 +00:00
|
|
|
((SGInterpolator*)globals->get_subsystem_mgr()
|
|
|
|
->get_group(SGSubsystemMgr::INIT)->get_subsystem("interpolator"))
|
2003-12-05 01:54:39 +00:00
|
|
|
->interpolate(node, nPoints, values, deltas);
|
2004-11-15 18:15:33 +00:00
|
|
|
|
2008-01-22 20:09:02 +00:00
|
|
|
delete[] values;
|
|
|
|
delete[] deltas;
|
2004-11-15 18:15:33 +00:00
|
|
|
return naNil();
|
2003-12-05 01:54:39 +00:00
|
|
|
}
|
|
|
|
|
2005-04-18 19:49:13 +00:00
|
|
|
// This is a better RNG than the one in the default Nasal distribution
|
|
|
|
// (which is based on the C library rand() implementation). It will
|
|
|
|
// override.
|
|
|
|
static naRef f_rand(naContext c, naRef me, int argc, naRef* args)
|
2003-12-05 01:54:39 +00:00
|
|
|
{
|
|
|
|
return naNum(sg_random());
|
|
|
|
}
|
|
|
|
|
2006-01-09 15:29:24 +00:00
|
|
|
static naRef f_srand(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
sg_srandom_time();
|
|
|
|
return naNum(0);
|
|
|
|
}
|
|
|
|
|
2008-06-19 17:18:42 +00:00
|
|
|
static naRef f_abort(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
abort();
|
|
|
|
return naNil();
|
|
|
|
}
|
|
|
|
|
2006-01-09 03:48:14 +00:00
|
|
|
// Return an array listing of all files in a directory
|
|
|
|
static naRef f_directory(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 1 || !naIsString(args[0]))
|
|
|
|
naRuntimeError(c, "bad arguments to directory()");
|
|
|
|
naRef ldir = args[0];
|
|
|
|
ulDir* dir = ulOpenDir(naStr_data(args[0]));
|
|
|
|
if(!dir) return naNil();
|
|
|
|
naRef result = naNewVector(c);
|
|
|
|
ulDirEnt* dent;
|
|
|
|
while((dent = ulReadDir(dir)))
|
|
|
|
naVec_append(result, naStr_fromdata(naNewString(c), dent->d_name,
|
|
|
|
strlen(dent->d_name)));
|
|
|
|
ulCloseDir(dir);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// Parse XML file.
|
|
|
|
// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
|
|
|
|
//
|
2007-09-30 11:56:21 +00:00
|
|
|
// <path> ... absolute path to an XML file
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// <start-tag> ... callback function with two args: tag name, attribute hash
|
|
|
|
// <end-tag> ... callback function with one arg: tag name
|
2007-07-01 11:07:53 +00:00
|
|
|
// <data> ... callback function with one arg: data
|
|
|
|
// <pi> ... callback function with two args: target, data
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
// (pi = "processing instruction")
|
|
|
|
// All four callback functions are optional and default to nil.
|
2008-07-22 20:26:17 +00:00
|
|
|
// The function returns nil on error, or the validated file name otherwise.
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc < 1 || !naIsString(args[0]))
|
2007-07-01 11:07:53 +00:00
|
|
|
naRuntimeError(c, "parsexml(): path argument missing or not a string");
|
|
|
|
if(argc > 5) argc = 5;
|
|
|
|
for(int i=1; i<argc; i++)
|
|
|
|
if(!(naIsNil(args[i]) || naIsFunc(args[i])))
|
|
|
|
naRuntimeError(c, "parsexml(): callback argument not a function");
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
|
2008-07-22 20:26:17 +00:00
|
|
|
const char* file = fgValidatePath(naStr_data(args[0]), false);
|
|
|
|
if(!file) {
|
|
|
|
naRuntimeError(c, "parsexml(): reading '%s' denied "
|
|
|
|
"(unauthorized access)", naStr_data(args[0]));
|
|
|
|
return naNil();
|
|
|
|
}
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
std::ifstream input(file);
|
|
|
|
NasalXMLVisitor visitor(c, argc, args);
|
|
|
|
try {
|
|
|
|
readXML(input, visitor);
|
|
|
|
} catch (const sg_exception& e) {
|
2007-10-14 07:51:11 +00:00
|
|
|
naRuntimeError(c, "parsexml(): file '%s' %s",
|
|
|
|
file, e.getFormattedMessage().c_str());
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
return naNil();
|
|
|
|
}
|
2008-07-22 20:26:17 +00:00
|
|
|
return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
}
|
|
|
|
|
2007-05-14 16:24:41 +00:00
|
|
|
// Return UNIX epoch time in seconds.
|
|
|
|
static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
#ifdef WIN32
|
|
|
|
FILETIME ft;
|
|
|
|
GetSystemTimeAsFileTime(&ft);
|
|
|
|
double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
|
|
|
|
// Converts from 100ns units in 1601 epoch to unix epoch in sec
|
|
|
|
return naNum((t * 1e-7) - 11644473600.0);
|
|
|
|
#else
|
|
|
|
time_t t;
|
|
|
|
struct timeval td;
|
|
|
|
do { t = time(0); gettimeofday(&td, 0); } while(t != time(0));
|
|
|
|
return naNum(t + 1e-6 * td.tv_usec);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2007-06-16 18:22:20 +00:00
|
|
|
// Convert a cartesian point to a geodetic lat/lon/altitude.
|
|
|
|
static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
double lat, lon, alt, xyz[3];
|
|
|
|
if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
xyz[i] = naNumValue(args[i]).num;
|
|
|
|
sgCartToGeod(xyz, &lat, &lon, &alt);
|
2007-07-01 11:07:53 +00:00
|
|
|
lat *= SG_RADIANS_TO_DEGREES;
|
|
|
|
lon *= SG_RADIANS_TO_DEGREES;
|
2007-06-16 18:22:20 +00:00
|
|
|
naRef vec = naNewVector(c);
|
|
|
|
naVec_append(vec, naNum(lat));
|
|
|
|
naVec_append(vec, naNum(lon));
|
|
|
|
naVec_append(vec, naNum(alt));
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
add geodinfo(<lat>, <lon>) function that returns an array [<elev>, <matdata>]
or nil if no terrain intersection could be made (tile not loaded yet).
<matdata> is a hash with information about the surface material, or nil
if no material is assigned (shouldn't really happen, but one never knows).
Example:
var ac = geo.aircraft_position();
var data = geoddata(ac.lat(), ac.lon());
debug.dump(data);
# which outputs
[ 294.5862574369132, { light_coverage : 0, bumpiness : 0, load_resistance : 1e+30,
solid : 1, names : [ "pc_taxiway", "dirt_rwytaxiway" ], friction_factor : 1,
rolling_friction : 0.02 } ]
With this information it can be determined how far an object would sink in,
if the coordinate is on a runway, etc.
foreach (var n; data[1].names)
if (string.match(n, "p[ac]_*"))
im_on_a_runway();
2007-06-23 15:25:41 +00:00
|
|
|
// Convert a geodetic lat/lon/altitude to a cartesian point.
|
2007-06-16 18:22:20 +00:00
|
|
|
static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
|
2007-07-01 11:07:53 +00:00
|
|
|
double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
|
|
|
|
double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
|
|
|
|
double alt = naNumValue(args[2]).num;
|
2007-06-16 18:22:20 +00:00
|
|
|
double xyz[3];
|
|
|
|
sgGeodToCart(lat, lon, alt, xyz);
|
|
|
|
naRef vec = naNewVector(c);
|
|
|
|
naVec_append(vec, naNum(xyz[0]));
|
|
|
|
naVec_append(vec, naNum(xyz[1]));
|
|
|
|
naVec_append(vec, naNum(xyz[2]));
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
add geodinfo(<lat>, <lon>) function that returns an array [<elev>, <matdata>]
or nil if no terrain intersection could be made (tile not loaded yet).
<matdata> is a hash with information about the surface material, or nil
if no material is assigned (shouldn't really happen, but one never knows).
Example:
var ac = geo.aircraft_position();
var data = geoddata(ac.lat(), ac.lon());
debug.dump(data);
# which outputs
[ 294.5862574369132, { light_coverage : 0, bumpiness : 0, load_resistance : 1e+30,
solid : 1, names : [ "pc_taxiway", "dirt_rwytaxiway" ], friction_factor : 1,
rolling_friction : 0.02 } ]
With this information it can be determined how far an object would sink in,
if the coordinate is on a runway, etc.
foreach (var n; data[1].names)
if (string.match(n, "p[ac]_*"))
im_on_a_runway();
2007-06-23 15:25:41 +00:00
|
|
|
// For given geodetic point return array with elevation, and a material data
|
|
|
|
// hash, or nil if there's no information available (tile not loaded). If
|
|
|
|
// information about the material isn't available, then nil is returned instead
|
|
|
|
// of the hash.
|
|
|
|
static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
|
|
|
#define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
|
|
|
|
if(argc != 2) naRuntimeError(c, "geodinfo() expects 2 arguments: lat, lon");
|
|
|
|
double lat = naNumValue(args[0]).num;
|
|
|
|
double lon = naNumValue(args[1]).num;
|
|
|
|
double elev;
|
|
|
|
const SGMaterial *mat;
|
|
|
|
if(!globals->get_scenery()->get_elevation_m(lat, lon, 10000.0, elev, &mat))
|
|
|
|
return naNil();
|
|
|
|
naRef vec = naNewVector(c);
|
|
|
|
naVec_append(vec, naNum(elev));
|
|
|
|
naRef matdata = naNil();
|
|
|
|
if(mat) {
|
|
|
|
matdata = naNewHash(c);
|
|
|
|
naRef names = naNewVector(c);
|
|
|
|
const vector<string> n = mat->get_names();
|
|
|
|
for(unsigned int i=0; i<n.size(); i++)
|
|
|
|
naVec_append(names, naStr_fromdata(naNewString(c),
|
|
|
|
const_cast<char*>(n[i].c_str()), n[i].size()));
|
|
|
|
HASHSET("names", 5, names);
|
|
|
|
HASHSET("solid", 5, naNum(mat->get_solid()));
|
|
|
|
HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
|
|
|
|
HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
|
|
|
|
HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
|
|
|
|
HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
|
|
|
|
HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
|
|
|
|
}
|
|
|
|
naVec_append(vec, matdata);
|
|
|
|
return vec;
|
|
|
|
#undef HASHSET
|
|
|
|
}
|
|
|
|
|
2007-10-05 22:15:55 +00:00
|
|
|
|
|
|
|
class airport_filter : public FGAirportSearchFilter {
|
2007-10-11 07:53:17 +00:00
|
|
|
virtual bool pass(FGAirport *a) { return a->isAirport(); }
|
2007-10-05 22:15:55 +00:00
|
|
|
} airport;
|
2007-10-14 07:51:11 +00:00
|
|
|
class seaport_filter : public FGAirportSearchFilter {
|
|
|
|
virtual bool pass(FGAirport *a) { return a->isSeaport(); }
|
|
|
|
} seaport;
|
|
|
|
class heliport_filter : public FGAirportSearchFilter {
|
|
|
|
virtual bool pass(FGAirport *a) { return a->isHeliport(); }
|
|
|
|
} heliport;
|
|
|
|
|
|
|
|
// Returns data hash for particular or nearest airport of a <type>, or nil
|
|
|
|
// on error. Only one side of each runway is contained.
|
|
|
|
//
|
|
|
|
// airportinfo(<id>); e.g. "KSFO"
|
|
|
|
// airportinfo(<type>); type := ("airport"|"seaport"|"heliport")
|
|
|
|
// airportinfo() same as airportinfo("airport")
|
|
|
|
// airportinfo(<lat>, <lon> [, <type>]);
|
2007-10-02 15:31:03 +00:00
|
|
|
static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
|
|
|
|
{
|
2007-10-14 07:51:11 +00:00
|
|
|
static SGConstPropertyNode_ptr latn = fgGetNode("/position/latitude-deg", true);
|
|
|
|
static SGConstPropertyNode_ptr lonn = fgGetNode("/position/longitude-deg", true);
|
|
|
|
double lat, lon;
|
2007-10-02 15:31:03 +00:00
|
|
|
|
|
|
|
FGAirportList *aptlst = globals->get_airports();
|
|
|
|
FGAirport *apt;
|
2007-10-14 07:51:11 +00:00
|
|
|
if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
|
|
|
|
lat = args[0].num;
|
|
|
|
lon = args[1].num;
|
|
|
|
args += 2;
|
|
|
|
argc -= 2;
|
|
|
|
} else {
|
|
|
|
lat = latn->getDoubleValue();
|
|
|
|
lon = lonn->getDoubleValue();
|
|
|
|
}
|
2008-08-15 18:48:11 +00:00
|
|
|
|
|
|
|
double maxRange = 360.0; // expose this? or pick a smaller value?
|
|
|
|
|
2007-10-14 07:51:11 +00:00
|
|
|
if(argc == 0) {
|
2008-08-15 18:48:11 +00:00
|
|
|
apt = aptlst->search(lon, lat, maxRange, airport);
|
2007-10-14 07:51:11 +00:00
|
|
|
} else if(argc == 1 && naIsString(args[0])) {
|
|
|
|
const char *s = naStr_data(args[0]);
|
2008-08-15 18:48:11 +00:00
|
|
|
if(!strcmp(s, "airport")) apt = aptlst->search(lon, lat, maxRange, airport);
|
|
|
|
else if(!strcmp(s, "seaport")) apt = aptlst->search(lon, lat, maxRange, seaport);
|
|
|
|
else if(!strcmp(s, "heliport")) apt = aptlst->search(lon, lat, maxRange, heliport);
|
2007-10-14 07:51:11 +00:00
|
|
|
else apt = aptlst->search(s);
|
|
|
|
} else {
|
2007-10-02 15:31:03 +00:00
|
|
|
naRuntimeError(c, "airportinfo() with invalid function arguments");
|
|
|
|
return naNil();
|
|
|
|
}
|
2007-10-18 11:43:38 +00:00
|
|
|
if(!apt) return naNil();
|
2007-10-02 15:31:03 +00:00
|
|
|
|
|
|
|
string id = apt->getId();
|
|
|
|
string name = apt->getName();
|
2008-08-14 18:13:39 +00:00
|
|
|
|
2007-10-02 15:31:03 +00:00
|
|
|
// set runway hash
|
|
|
|
naRef rwys = naNewHash(c);
|
2008-08-14 18:13:39 +00:00
|
|
|
for (unsigned int r=0; r<apt->numRunways(); ++r) {
|
James Turner:
Convert FGRunway to be heap-based, and inherit FGPositioned. This is a large, ugly change, since FGRunway was essentially a plain struct, with no accessors or abstraction. This change adds various helpers and accessors to FGRunway, but doesn't change many places to use them - that will be a follow up series of patches. It's still a large patch, but outside of FGAirport and FGRunway, mostly mechanical search-and-replace.
An interesting part of this change is that reciprocal runways now exist as independent objects, rather than being created on the fly by the search methods. This simplifies some pieces of code that search for and iterate runways. For users who only want one 'end' of a runway, the new 'isReciprocal' predicate allows them to ignore the 'other' end. Current the only user of this is the 'ground-radar' ATC feature. If we had data on which runways are truly 'single-ended', it would now be trivial to use this in the airport loader to *not* create the reciprocal.
2008-09-11 08:38:09 +00:00
|
|
|
FGRunway* rwy(apt->getRunwayByIndex(r));
|
2008-08-14 18:13:39 +00:00
|
|
|
|
|
|
|
naRef rwyid = naStr_fromdata(naNewString(c),
|
James Turner:
Convert FGRunway to be heap-based, and inherit FGPositioned. This is a large, ugly change, since FGRunway was essentially a plain struct, with no accessors or abstraction. This change adds various helpers and accessors to FGRunway, but doesn't change many places to use them - that will be a follow up series of patches. It's still a large patch, but outside of FGAirport and FGRunway, mostly mechanical search-and-replace.
An interesting part of this change is that reciprocal runways now exist as independent objects, rather than being created on the fly by the search methods. This simplifies some pieces of code that search for and iterate runways. For users who only want one 'end' of a runway, the new 'isReciprocal' predicate allows them to ignore the 'other' end. Current the only user of this is the 'ground-radar' ATC feature. If we had data on which runways are truly 'single-ended', it would now be trivial to use this in the airport loader to *not* create the reciprocal.
2008-09-11 08:38:09 +00:00
|
|
|
const_cast<char *>(rwy->ident().c_str()),
|
|
|
|
rwy->ident().length());
|
2007-12-05 10:57:51 +00:00
|
|
|
|
2008-08-14 18:13:39 +00:00
|
|
|
naRef rwydata = naNewHash(c);
|
2007-10-02 15:31:03 +00:00
|
|
|
#define HASHSET(s,l,n) naHash_set(rwydata, naStr_fromdata(naNewString(c),s,l),n)
|
2008-08-14 18:13:39 +00:00
|
|
|
HASHSET("id", 2, rwyid);
|
James Turner:
Convert FGRunway to be heap-based, and inherit FGPositioned. This is a large, ugly change, since FGRunway was essentially a plain struct, with no accessors or abstraction. This change adds various helpers and accessors to FGRunway, but doesn't change many places to use them - that will be a follow up series of patches. It's still a large patch, but outside of FGAirport and FGRunway, mostly mechanical search-and-replace.
An interesting part of this change is that reciprocal runways now exist as independent objects, rather than being created on the fly by the search methods. This simplifies some pieces of code that search for and iterate runways. For users who only want one 'end' of a runway, the new 'isReciprocal' predicate allows them to ignore the 'other' end. Current the only user of this is the 'ground-radar' ATC feature. If we had data on which runways are truly 'single-ended', it would now be trivial to use this in the airport loader to *not* create the reciprocal.
2008-09-11 08:38:09 +00:00
|
|
|
HASHSET("lat", 3, naNum(rwy->latitude()));
|
|
|
|
HASHSET("lon", 3, naNum(rwy->longitude()));
|
|
|
|
HASHSET("heading", 7, naNum(rwy->headingDeg()));
|
|
|
|
HASHSET("length", 6, naNum(rwy->lengthM()));
|
|
|
|
HASHSET("width", 5, naNum(rwy->widthM()));
|
|
|
|
HASHSET("threshold1", 10, naNum(rwy->_displ_thresh * SG_FEET_TO_METER));
|
|
|
|
HASHSET("stopway1", 8, naNum(rwy->_stopway * SG_FEET_TO_METER));
|
2007-10-02 15:31:03 +00:00
|
|
|
#undef HASHSET
|
2008-08-14 18:13:39 +00:00
|
|
|
naHash_set(rwys, rwyid, rwydata);
|
2007-10-02 15:31:03 +00:00
|
|
|
}
|
2008-08-14 18:13:39 +00:00
|
|
|
|
2007-10-02 15:31:03 +00:00
|
|
|
// set airport hash
|
|
|
|
naRef aptdata = naNewHash(c);
|
|
|
|
#define HASHSET(s,l,n) naHash_set(aptdata, naStr_fromdata(naNewString(c),s,l),n)
|
|
|
|
HASHSET("id", 2, naStr_fromdata(naNewString(c),
|
|
|
|
const_cast<char *>(id.c_str()), id.length()));
|
|
|
|
HASHSET("name", 4, naStr_fromdata(naNewString(c),
|
|
|
|
const_cast<char *>(name.c_str()), name.length()));
|
|
|
|
HASHSET("lat", 3, naNum(apt->getLatitude()));
|
|
|
|
HASHSET("lon", 3, naNum(apt->getLongitude()));
|
2007-10-02 16:03:27 +00:00
|
|
|
HASHSET("elevation", 9, naNum(apt->getElevation() * SG_FEET_TO_METER));
|
2007-10-02 15:31:03 +00:00
|
|
|
HASHSET("has_metar", 9, naNum(apt->getMetar()));
|
|
|
|
HASHSET("runways", 7, rwys);
|
|
|
|
#undef HASHSET
|
|
|
|
return aptdata;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Table of extension functions. Terminate with zeros.
|
2007-10-15 16:28:40 +00:00
|
|
|
static struct { const char* name; naCFunction func; } funcs[] = {
|
2003-11-25 21:08:36 +00:00
|
|
|
{ "getprop", f_getprop },
|
|
|
|
{ "setprop", f_setprop },
|
|
|
|
{ "print", f_print },
|
2003-12-01 14:35:49 +00:00
|
|
|
{ "_fgcommand", f_fgcommand },
|
2003-11-25 21:08:36 +00:00
|
|
|
{ "settimer", f_settimer },
|
2005-12-16 19:11:03 +00:00
|
|
|
{ "_setlistener", f_setlistener },
|
2006-02-28 14:55:37 +00:00
|
|
|
{ "removelistener", f_removelistener },
|
2003-12-01 14:35:49 +00:00
|
|
|
{ "_cmdarg", f_cmdarg },
|
2003-12-05 01:54:39 +00:00
|
|
|
{ "_interpolate", f_interpolate },
|
|
|
|
{ "rand", f_rand },
|
2006-01-09 15:29:24 +00:00
|
|
|
{ "srand", f_srand },
|
2008-06-19 17:18:42 +00:00
|
|
|
{ "abort", f_abort },
|
2006-01-09 03:48:14 +00:00
|
|
|
{ "directory", f_directory },
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
{ "parsexml", f_parsexml },
|
2007-05-14 16:24:41 +00:00
|
|
|
{ "systime", f_systime },
|
2007-06-16 18:22:20 +00:00
|
|
|
{ "carttogeod", f_carttogeod },
|
|
|
|
{ "geodtocart", f_geodtocart },
|
add geodinfo(<lat>, <lon>) function that returns an array [<elev>, <matdata>]
or nil if no terrain intersection could be made (tile not loaded yet).
<matdata> is a hash with information about the surface material, or nil
if no material is assigned (shouldn't really happen, but one never knows).
Example:
var ac = geo.aircraft_position();
var data = geoddata(ac.lat(), ac.lon());
debug.dump(data);
# which outputs
[ 294.5862574369132, { light_coverage : 0, bumpiness : 0, load_resistance : 1e+30,
solid : 1, names : [ "pc_taxiway", "dirt_rwytaxiway" ], friction_factor : 1,
rolling_friction : 0.02 } ]
With this information it can be determined how far an object would sink in,
if the coordinate is on a runway, etc.
foreach (var n; data[1].names)
if (string.match(n, "p[ac]_*"))
im_on_a_runway();
2007-06-23 15:25:41 +00:00
|
|
|
{ "geodinfo", f_geodinfo },
|
2007-10-02 15:31:03 +00:00
|
|
|
{ "airportinfo", f_airportinfo },
|
2003-11-25 21:08:36 +00:00
|
|
|
{ 0, 0 }
|
|
|
|
};
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
naRef FGNasalSys::cmdArgGhost()
|
|
|
|
{
|
|
|
|
return propNodeGhost(_cmdArg);
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
void FGNasalSys::init()
|
|
|
|
{
|
2003-12-01 14:35:49 +00:00
|
|
|
int i;
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
_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.
|
2007-03-29 18:50:28 +00:00
|
|
|
_globals = naInit_std(_context);
|
2003-11-25 21:08:36 +00:00
|
|
|
naSave(_context, _globals);
|
|
|
|
hashset(_globals, "globals", _globals);
|
|
|
|
|
2007-03-29 18:50:28 +00:00
|
|
|
hashset(_globals, "math", naInit_math(_context));
|
|
|
|
hashset(_globals, "bits", naInit_bits(_context));
|
|
|
|
hashset(_globals, "io", naInit_io(_context));
|
|
|
|
hashset(_globals, "thread", naInit_thread(_context));
|
|
|
|
hashset(_globals, "utf8", naInit_utf8(_context));
|
2006-03-15 18:10:29 +00:00
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
// Add our custom extension functions:
|
2003-12-01 14:35:49 +00:00
|
|
|
for(i=0; funcs[i].name; i++)
|
2003-11-25 21:08:36 +00:00
|
|
|
hashset(_globals, funcs[i].name,
|
|
|
|
naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// And our SGPropertyNode wrapper
|
|
|
|
hashset(_globals, "props", genPropsModule());
|
|
|
|
|
2003-12-05 01:54:39 +00:00
|
|
|
// Make a "__gcsave" hash to hold the naRef objects which get
|
|
|
|
// passed to handles outside the interpreter (to protect them from
|
|
|
|
// begin garbage-collected).
|
|
|
|
_gcHash = naNewHash(_context);
|
|
|
|
hashset(_globals, "__gcsave", _gcHash);
|
2003-11-25 21:08:36 +00:00
|
|
|
|
|
|
|
// 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;
|
2005-06-14 19:57:24 +00:00
|
|
|
loadModule(fullpath, file.base().c_str());
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
2006-03-13 19:47:22 +00:00
|
|
|
ulCloseDir(dir);
|
2007-01-23 15:53:04 +00:00
|
|
|
|
|
|
|
// set signal and remove node to avoid restoring at reinit
|
|
|
|
const char *s = "nasal-dir-initialized";
|
|
|
|
SGPropertyNode *signal = fgGetNode("/sim/signals", true);
|
|
|
|
signal->setBoolValue(s, true);
|
|
|
|
signal->removeChildren(s);
|
2003-12-01 14:35:49 +00:00
|
|
|
|
|
|
|
// Pull scripts out of the property tree, too
|
|
|
|
loadPropertyScripts();
|
|
|
|
}
|
|
|
|
|
2007-01-23 16:47:04 +00:00
|
|
|
void FGNasalSys::update(double)
|
|
|
|
{
|
2007-10-18 11:43:38 +00:00
|
|
|
if(!_dead_listener.empty()) {
|
|
|
|
vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
|
|
|
|
for(it = _dead_listener.begin(); it != end; ++it) delete *it;
|
|
|
|
_dead_listener.clear();
|
2007-01-23 16:47:04 +00:00
|
|
|
}
|
2007-10-24 18:07:02 +00:00
|
|
|
|
|
|
|
// The global context is a legacy thing. We use dynamically
|
|
|
|
// created contexts for naCall() now, so that we can call them
|
|
|
|
// recursively. But there are still spots that want to use it for
|
|
|
|
// naNew*() calls, which end up leaking memory because the context
|
|
|
|
// only clears out its temporary vector when it's *used*. So just
|
|
|
|
// junk it and fetch a new/reinitialized one every frame. This is
|
|
|
|
// clumsy: the right solution would use the dynamic context in all
|
|
|
|
// cases and eliminate _context entirely. But that's more work,
|
|
|
|
// and this works fine (yes, they say "New" and "Free", but
|
|
|
|
// they're very fast, just trust me). -Andy
|
|
|
|
naFreeContext(_context);
|
|
|
|
_context = naNewContext();
|
2007-01-23 16:47:04 +00:00
|
|
|
}
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// Loads the scripts found under /nasal in the global tree
|
|
|
|
void FGNasalSys::loadPropertyScripts()
|
|
|
|
{
|
|
|
|
SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
|
|
|
|
if(!nasal) return;
|
|
|
|
|
|
|
|
for(int i=0; i<nasal->nChildren(); i++) {
|
|
|
|
SGPropertyNode* n = nasal->getChild(i);
|
|
|
|
|
|
|
|
const char* module = n->getName();
|
|
|
|
if(n->hasChild("module"))
|
|
|
|
module = n->getStringValue("module");
|
|
|
|
|
2005-03-16 21:36:55 +00:00
|
|
|
// allow multiple files to be specified within in a single
|
|
|
|
// Nasal module tag
|
|
|
|
int j = 0;
|
|
|
|
SGPropertyNode *fn;
|
|
|
|
bool file_specified = false;
|
|
|
|
while ( (fn = n->getChild("file", j)) != NULL ) {
|
|
|
|
file_specified = true;
|
|
|
|
const char* file = fn->getStringValue();
|
|
|
|
SGPath p(globals->get_fg_root());
|
|
|
|
p.append(file);
|
2005-06-14 19:57:24 +00:00
|
|
|
loadModule(p, module);
|
2005-03-16 21:36:55 +00:00
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Old code which only allowed a single file to be specified per module
|
|
|
|
/*
|
2003-12-01 14:35:49 +00:00
|
|
|
const char* file = n->getStringValue("file");
|
|
|
|
if(!n->hasChild("file")) file = 0; // Hrm...
|
|
|
|
if(file) {
|
|
|
|
SGPath p(globals->get_fg_root());
|
|
|
|
p.append(file);
|
2005-06-14 19:57:24 +00:00
|
|
|
loadModule(p, module);
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
2005-03-16 21:36:55 +00:00
|
|
|
*/
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
const char* src = n->getStringValue("script");
|
|
|
|
if(!n->hasChild("script")) src = 0; // Hrm...
|
|
|
|
if(src)
|
2005-06-19 17:09:03 +00:00
|
|
|
createModule(module, n->getPath(), src, strlen(src));
|
2003-12-01 14:35:49 +00:00
|
|
|
|
2005-03-16 21:36:55 +00:00
|
|
|
if(!file_specified && !src)
|
2003-12-01 14:35:49 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
|
|
|
|
"no <file> or <script> defined in " <<
|
|
|
|
"/nasal/" << module);
|
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Logs a runtime error, with stack trace, to the FlightGear log stream
|
2006-01-27 19:51:25 +00:00
|
|
|
void FGNasalSys::logError(naContext context)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT,
|
2006-01-27 19:51:25 +00:00
|
|
|
"Nasal runtime error: " << naGetError(context));
|
2003-11-25 21:08:36 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT,
|
2006-01-27 19:51:25 +00:00
|
|
|
" at " << naStr_data(naGetSourceFile(context, 0)) <<
|
|
|
|
", line " << naGetLine(context, 0));
|
|
|
|
for(int i=1; i<naStackDepth(context); i++)
|
2003-11-25 21:08:36 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_ALERT,
|
2006-01-27 19:51:25 +00:00
|
|
|
" called from: " << naStr_data(naGetSourceFile(context, i)) <<
|
|
|
|
", line " << naGetLine(context, i));
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reads a script file, executes it, and places the resulting
|
|
|
|
// namespace into the global namespace under the specified module
|
|
|
|
// name.
|
2005-06-14 19:57:24 +00:00
|
|
|
void FGNasalSys::loadModule(SGPath file, const char* module)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
char* buf = readfile(file.c_str(), &len);
|
2003-12-01 14:35:49 +00:00
|
|
|
if(!buf) {
|
|
|
|
SG_LOG(SG_NASAL, SG_ALERT,
|
|
|
|
"Nasal error: could not read script file " << file.c_str()
|
|
|
|
<< " into module " << module);
|
|
|
|
return;
|
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2005-06-19 17:09:03 +00:00
|
|
|
createModule(module, file.c_str(), buf, len);
|
2003-11-25 21:08:36 +00:00
|
|
|
delete[] buf;
|
2003-12-01 14:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse and run. Save the local variables namespace, as it will
|
2006-04-27 16:40:04 +00:00
|
|
|
// become a sub-object of globals. The optional "arg" argument can be
|
|
|
|
// used to pass an associated property node to the module, which can then
|
|
|
|
// be accessed via cmdarg(). (This is, for example, used by XML dialogs.)
|
2005-06-19 17:09:03 +00:00
|
|
|
void FGNasalSys::createModule(const char* moduleName, const char* fileName,
|
2006-04-27 15:56:51 +00:00
|
|
|
const char* src, int len, const SGPropertyNode* arg)
|
2003-12-01 14:35:49 +00:00
|
|
|
{
|
|
|
|
naRef code = parse(fileName, src, len);
|
2003-11-25 21:08:36 +00:00
|
|
|
if(naIsNil(code))
|
|
|
|
return;
|
|
|
|
|
2003-12-01 14:35:49 +00:00
|
|
|
// See if we already have a module hash to use. This allows the
|
|
|
|
// user to, for example, add functions to the built-in math
|
|
|
|
// module. Make a new one if necessary.
|
|
|
|
naRef locals;
|
|
|
|
naRef modname = naNewString(_context);
|
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
|
|
|
if(!naHash_get(_globals, modname, &locals))
|
|
|
|
locals = naNewHash(_context);
|
|
|
|
|
2006-04-27 15:56:51 +00:00
|
|
|
_cmdArg = (SGPropertyNode*)arg;
|
|
|
|
|
2007-10-15 16:28:40 +00:00
|
|
|
call(code, 0, 0, locals);
|
2003-12-01 14:35:49 +00:00
|
|
|
hashset(_globals, moduleName, locals);
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-08 10:35:20 +00:00
|
|
|
void FGNasalSys::deleteModule(const char* moduleName)
|
|
|
|
{
|
|
|
|
naRef modname = naNewString(_context);
|
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
|
|
|
naHash_delete(_globals, modname);
|
|
|
|
}
|
|
|
|
|
2003-11-25 21:08:36 +00:00
|
|
|
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)
|
|
|
|
{
|
|
|
|
const char* nasal = arg->getStringValue("script");
|
2005-06-19 17:09:03 +00:00
|
|
|
const char* moduleName = arg->getStringValue("module");
|
2005-06-20 18:53:00 +00:00
|
|
|
naRef code = parse(arg->getPath(true), nasal, strlen(nasal));
|
2003-11-25 21:08:36 +00:00
|
|
|
if(naIsNil(code)) return false;
|
2005-06-19 17:09:03 +00:00
|
|
|
|
2007-03-29 18:50:28 +00:00
|
|
|
// Commands can be run "in" a module. Make sure that module
|
|
|
|
// exists, and set it up as the local variables hash for the
|
|
|
|
// command.
|
2005-06-19 17:09:03 +00:00
|
|
|
naRef locals = naNil();
|
2006-03-08 16:06:32 +00:00
|
|
|
if(moduleName[0]) {
|
2007-03-29 18:50:28 +00:00
|
|
|
naRef modname = naNewString(_context);
|
2005-06-19 17:09:03 +00:00
|
|
|
naStr_fromdata(modname, (char*)moduleName, strlen(moduleName));
|
2007-03-29 18:50:28 +00:00
|
|
|
if(!naHash_get(_globals, modname, &locals)) {
|
|
|
|
locals = naNewHash(_context);
|
|
|
|
naHash_set(_globals, modname, locals);
|
|
|
|
}
|
2005-06-19 17:09:03 +00:00
|
|
|
}
|
2007-03-29 18:50:28 +00:00
|
|
|
|
|
|
|
// Cache this command's argument for inspection via cmdarg(). For
|
2003-12-01 14:35:49 +00:00
|
|
|
// performance reasons, we won't bother with it if the invoked
|
|
|
|
// code doesn't need it.
|
|
|
|
_cmdArg = (SGPropertyNode*)arg;
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2007-10-15 16:28:40 +00:00
|
|
|
call(code, 0, 0, locals);
|
2006-07-19 19:46:53 +00:00
|
|
|
return true;
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2005-11-09 20:34:46 +00:00
|
|
|
// that "real world" time (rather than simulator time) is to be used.
|
2003-11-25 21:08:36 +00:00
|
|
|
//
|
|
|
|
// 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,
|
2003-12-05 01:54:39 +00:00
|
|
|
// they are inserted into a globals.__gcsave hash and removed on
|
2003-11-25 21:08:36 +00:00
|
|
|
// expiration.
|
2007-05-12 18:15:45 +00:00
|
|
|
void FGNasalSys::setTimer(naContext c, int argc, naRef* args)
|
2003-11-25 21:08:36 +00:00
|
|
|
{
|
|
|
|
// Extract the handler, delta, and simtime arguments:
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef handler = argc > 0 ? args[0] : naNil();
|
2007-05-12 18:15:45 +00:00
|
|
|
if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
|
|
|
|
naRuntimeError(c, "settimer() with invalid function argument");
|
2003-11-25 21:08:36 +00:00
|
|
|
return;
|
2007-05-12 18:15:45 +00:00
|
|
|
}
|
2003-11-25 21:08:36 +00:00
|
|
|
|
2005-04-18 19:49:13 +00:00
|
|
|
naRef delta = argc > 1 ? args[1] : naNil();
|
2007-05-12 18:15:45 +00:00
|
|
|
if(naIsNil(delta)) {
|
|
|
|
naRuntimeError(c, "settimer() with invalid time argument");
|
|
|
|
return;
|
|
|
|
}
|
2006-02-28 14:55:37 +00:00
|
|
|
|
2005-11-09 20:34:46 +00:00
|
|
|
bool simtime = (argc > 2 && naTrue(args[2])) ? false : true;
|
2003-11-25 21:08:36 +00:00
|
|
|
|
|
|
|
// Generate and register a C++ timer handler
|
|
|
|
NasalTimer* t = new NasalTimer;
|
|
|
|
t->handler = handler;
|
2003-12-05 01:54:39 +00:00
|
|
|
t->gcKey = gcSave(handler);
|
2003-11-25 21:08:36 +00:00
|
|
|
t->nasal = this;
|
|
|
|
|
|
|
|
globals->get_event_mgr()->addEvent("NasalTimer",
|
|
|
|
t, &NasalTimer::timerExpired,
|
|
|
|
delta.num, simtime);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::handleTimer(NasalTimer* t)
|
|
|
|
{
|
2007-10-15 16:28:40 +00:00
|
|
|
call(t->handler, 0, 0, naNil());
|
2003-12-05 01:54:39 +00:00
|
|
|
gcRelease(t->gcKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
int FGNasalSys::gcSave(naRef r)
|
|
|
|
{
|
|
|
|
int key = _nextGCKey++;
|
|
|
|
naHash_set(_gcHash, naNum(key), r);
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::gcRelease(int key)
|
|
|
|
{
|
|
|
|
naHash_delete(_gcHash, naNum(key));
|
2003-11-25 21:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalSys::NasalTimer::timerExpired()
|
|
|
|
{
|
|
|
|
nasal->handleTimer(this);
|
|
|
|
delete this;
|
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2006-02-28 14:55:37 +00:00
|
|
|
int FGNasalSys::_listenerId = 0;
|
|
|
|
|
2007-10-14 18:01:26 +00:00
|
|
|
// setlistener(<property>, <func> [, <initial=0> [, <persistent=1>]])
|
|
|
|
// Attaches a callback function to a property (specified as a global
|
|
|
|
// property path string or a SGPropertyNode_ptr* ghost). If the third,
|
|
|
|
// optional argument (default=0) is set to 1, then the function is also
|
|
|
|
// called initially. If the fourth, optional argument is set to 0, then the
|
|
|
|
// function is only called when the property node value actually changes.
|
|
|
|
// Otherwise it's called independent of the value whenever the node is
|
|
|
|
// written to (default). The setlistener() function returns a unique
|
|
|
|
// id number, which is to be used as argument to the removelistener()
|
|
|
|
// function.
|
2007-02-03 16:46:39 +00:00
|
|
|
naRef FGNasalSys::setListener(naContext c, int argc, naRef* args)
|
2005-12-16 19:11:03 +00:00
|
|
|
{
|
2006-02-28 14:55:37 +00:00
|
|
|
SGPropertyNode_ptr node;
|
2005-12-16 19:11:03 +00:00
|
|
|
naRef prop = argc > 0 ? args[0] : naNil();
|
|
|
|
if(naIsString(prop)) node = fgGetNode(naStr_data(prop), true);
|
|
|
|
else if(naIsGhost(prop)) node = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
|
2007-02-03 16:46:39 +00:00
|
|
|
else {
|
|
|
|
naRuntimeError(c, "setlistener() with invalid property argument");
|
|
|
|
return naNil();
|
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2007-01-23 15:53:04 +00:00
|
|
|
if(node->isTied())
|
2006-07-16 11:48:22 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_DEBUG, "Attaching listener to tied property " <<
|
2006-07-16 11:30:33 +00:00
|
|
|
node->getPath());
|
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
naRef code = argc > 1 ? args[1] : naNil();
|
|
|
|
if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
|
2007-02-03 16:46:39 +00:00
|
|
|
naRuntimeError(c, "setlistener() with invalid function argument");
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNil();
|
2007-02-03 16:46:39 +00:00
|
|
|
}
|
2005-12-16 19:11:03 +00:00
|
|
|
|
2007-10-24 18:07:02 +00:00
|
|
|
int type = argc > 3 && naIsNum(args[3]) ? (int)args[3].num : 1;
|
2007-10-18 11:43:38 +00:00
|
|
|
FGNasalListener *nl = new FGNasalListener(node, code, this,
|
|
|
|
gcSave(code), _listenerId, type);
|
2007-10-14 18:01:26 +00:00
|
|
|
|
|
|
|
bool initial = argc > 2 && naTrue(args[2]);
|
|
|
|
node->addChangeListener(nl, initial);
|
2006-03-02 10:41:48 +00:00
|
|
|
|
|
|
|
_listener[_listenerId] = nl;
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNum(_listenerId++);
|
|
|
|
}
|
|
|
|
|
|
|
|
// removelistener(int) extension function. The argument is the id of
|
|
|
|
// a listener as returned by the setlistener() function.
|
2007-01-17 13:56:22 +00:00
|
|
|
naRef FGNasalSys::removeListener(naContext c, int argc, naRef* args)
|
2006-02-28 14:55:37 +00:00
|
|
|
{
|
|
|
|
naRef id = argc > 0 ? args[0] : naNil();
|
2007-01-23 16:47:04 +00:00
|
|
|
map<int, FGNasalListener *>::iterator it = _listener.find(int(id.num));
|
2007-01-17 13:56:22 +00:00
|
|
|
|
2007-01-23 16:47:04 +00:00
|
|
|
if(!naIsNum(id) || it == _listener.end() || it->second->_dead) {
|
2007-01-17 13:56:22 +00:00
|
|
|
naRuntimeError(c, "removelistener() with invalid listener id");
|
2006-02-28 14:55:37 +00:00
|
|
|
return naNil();
|
2007-01-17 13:56:22 +00:00
|
|
|
}
|
2006-02-28 14:55:37 +00:00
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
it->second->_dead = true;
|
|
|
|
_dead_listener.push_back(it->second);
|
2007-01-23 16:47:04 +00:00
|
|
|
_listener.erase(it);
|
2006-03-02 10:41:48 +00:00
|
|
|
return naNum(_listener.size());
|
2005-12-16 19:11:03 +00:00
|
|
|
}
|
|
|
|
|
2006-03-09 09:04:03 +00:00
|
|
|
|
|
|
|
|
2006-03-20 07:13:10 +00:00
|
|
|
// FGNasalListener class.
|
|
|
|
|
2007-10-18 11:43:38 +00:00
|
|
|
FGNasalListener::FGNasalListener(SGPropertyNode *node, naRef code,
|
2007-10-15 16:28:40 +00:00
|
|
|
FGNasalSys* nasal, int key, int id, int type) :
|
2006-03-20 07:13:10 +00:00
|
|
|
_node(node),
|
2007-10-18 11:43:38 +00:00
|
|
|
_code(code),
|
2006-03-20 07:13:10 +00:00
|
|
|
_gcKey(key),
|
2007-04-27 14:30:05 +00:00
|
|
|
_id(id),
|
2006-03-20 07:13:10 +00:00
|
|
|
_nas(nasal),
|
2007-10-15 16:28:40 +00:00
|
|
|
_type(type),
|
2007-01-23 16:47:04 +00:00
|
|
|
_active(0),
|
2007-10-12 17:24:43 +00:00
|
|
|
_dead(false),
|
|
|
|
_first_call(true),
|
|
|
|
_last_int(0L),
|
|
|
|
_last_float(0.0)
|
2006-03-20 07:13:10 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
FGNasalListener::~FGNasalListener()
|
|
|
|
{
|
2006-06-10 22:21:22 +00:00
|
|
|
_node->removeChangeListener(this);
|
2006-03-20 07:13:10 +00:00
|
|
|
_nas->gcRelease(_gcKey);
|
|
|
|
}
|
|
|
|
|
2007-10-16 15:15:41 +00:00
|
|
|
void FGNasalListener::call(SGPropertyNode* which, naRef mode)
|
2006-03-20 07:13:10 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_active || _dead) return;
|
2007-10-15 16:28:40 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_DEBUG, "trigger listener #" << _id);
|
|
|
|
_active++;
|
2007-10-16 15:15:41 +00:00
|
|
|
naRef arg[4];
|
|
|
|
arg[0] = _nas->propNodeGhost(which);
|
|
|
|
arg[1] = _nas->propNodeGhost(_node);
|
|
|
|
arg[2] = mode; // value changed, child added/removed
|
|
|
|
arg[3] = naNum(_node != which); // child event?
|
|
|
|
_nas->_cmdArg = _node;
|
2007-10-18 11:43:38 +00:00
|
|
|
_nas->call(_code, 4, arg, naNil());
|
2007-10-15 16:28:40 +00:00
|
|
|
_active--;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FGNasalListener::valueChanged(SGPropertyNode* node)
|
|
|
|
{
|
2007-10-18 11:43:38 +00:00
|
|
|
if(_type < 2 && node != _node) return; // skip child events
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_type > 0 || changed(_node) || _first_call)
|
|
|
|
call(node, naNum(0));
|
2007-10-15 16:28:40 +00:00
|
|
|
|
2007-10-12 17:24:43 +00:00
|
|
|
_first_call = false;
|
2006-03-20 07:13:10 +00:00
|
|
|
}
|
|
|
|
|
2007-10-15 19:12:03 +00:00
|
|
|
void FGNasalListener::childAdded(SGPropertyNode*, SGPropertyNode* child)
|
2007-10-15 16:28:40 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_type == 2) call(child, naNum(1));
|
2007-10-15 16:28:40 +00:00
|
|
|
}
|
|
|
|
|
2007-10-15 19:12:03 +00:00
|
|
|
void FGNasalListener::childRemoved(SGPropertyNode*, SGPropertyNode* child)
|
2007-10-15 16:28:40 +00:00
|
|
|
{
|
2007-10-16 15:15:41 +00:00
|
|
|
if(_type == 2) call(child, naNum(-1));
|
2007-10-15 16:28:40 +00:00
|
|
|
}
|
|
|
|
|
2007-10-14 18:01:26 +00:00
|
|
|
bool FGNasalListener::changed(SGPropertyNode* node)
|
2007-10-12 17:24:43 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode::Type type = node->getType();
|
2007-10-14 18:01:26 +00:00
|
|
|
if(type == SGPropertyNode::NONE) return false;
|
|
|
|
if(type == SGPropertyNode::UNSPECIFIED) return true;
|
2007-10-12 17:24:43 +00:00
|
|
|
|
|
|
|
bool result;
|
|
|
|
switch(type) {
|
|
|
|
case SGPropertyNode::BOOL:
|
|
|
|
case SGPropertyNode::INT:
|
|
|
|
case SGPropertyNode::LONG:
|
|
|
|
{
|
|
|
|
long l = node->getLongValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = l != _last_int;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_int = l;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
case SGPropertyNode::FLOAT:
|
|
|
|
case SGPropertyNode::DOUBLE:
|
|
|
|
{
|
|
|
|
double d = node->getDoubleValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = d != _last_float;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_float = d;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
string s = node->getStringValue();
|
2007-10-14 18:01:26 +00:00
|
|
|
result = s != _last_string;
|
2007-10-12 17:24:43 +00:00
|
|
|
_last_string = s;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-03-20 07:13:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2006-03-09 09:04:03 +00:00
|
|
|
// FGNasalModelData class. If sgLoad3DModel() is called with a pointer to
|
2006-03-09 11:05:32 +00:00
|
|
|
// such a class, then it lets modelLoaded() run the <load> script, and the
|
|
|
|
// destructor the <unload> script. The latter happens when the model branch
|
|
|
|
// is removed from the scene graph.
|
2006-03-09 09:04:03 +00:00
|
|
|
|
|
|
|
void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop,
|
2006-10-29 19:30:21 +00:00
|
|
|
osg::Node *)
|
2006-03-09 09:04:03 +00:00
|
|
|
{
|
2008-03-22 09:31:06 +00:00
|
|
|
if(!prop)
|
|
|
|
return;
|
|
|
|
|
2008-04-29 15:35:09 +00:00
|
|
|
SGPropertyNode *load = prop->getNode("load");
|
2008-03-22 09:31:06 +00:00
|
|
|
_unload = prop->getNode("unload");
|
2007-01-23 15:53:04 +00:00
|
|
|
if(!load && !_unload)
|
2006-03-09 09:04:03 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
_module = path;
|
2007-10-01 15:59:24 +00:00
|
|
|
if(_props)
|
|
|
|
_module += ':' + _props->getPath();
|
2006-03-09 09:04:03 +00:00
|
|
|
const char *s = load ? load->getStringValue() : "";
|
2007-05-01 17:03:50 +00:00
|
|
|
nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
|
2006-03-09 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FGNasalModelData::~FGNasalModelData()
|
|
|
|
{
|
2007-01-23 15:53:04 +00:00
|
|
|
if(_module.empty())
|
2006-03-09 09:04:03 +00:00
|
|
|
return;
|
|
|
|
|
2007-05-01 17:03:50 +00:00
|
|
|
if(!nasalSys) {
|
2007-09-30 11:56:21 +00:00
|
|
|
SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
|
2006-03-09 17:32:24 +00:00
|
|
|
"without Nasal subsystem present.");
|
2006-03-09 13:30:28 +00:00
|
|
|
return;
|
2006-03-09 17:32:24 +00:00
|
|
|
}
|
2006-03-09 13:30:28 +00:00
|
|
|
|
2007-01-23 15:53:04 +00:00
|
|
|
if(_unload) {
|
2006-03-09 09:04:03 +00:00
|
|
|
const char *s = _unload->getStringValue();
|
2007-05-01 17:03:50 +00:00
|
|
|
nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
|
2006-03-09 09:04:03 +00:00
|
|
|
}
|
2007-05-01 17:03:50 +00:00
|
|
|
nasalSys->deleteModule(_module.c_str());
|
2006-03-09 09:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
|
|
|
|
// NasalXMLVisitor class: handles EasyXML visitor callback for parsexml()
|
|
|
|
//
|
|
|
|
NasalXMLVisitor::NasalXMLVisitor(naContext c, int argc, naRef* args) :
|
|
|
|
_c(naSubContext(c)),
|
2007-07-01 11:07:53 +00:00
|
|
|
_start_element(argc > 1 ? args[1] : naNil()),
|
|
|
|
_end_element(argc > 2 ? args[2] : naNil()),
|
|
|
|
_data(argc > 3 ? args[3] : naNil()),
|
|
|
|
_pi(argc > 4 ? args[4] : naNil())
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::startElement(const char* tag, const XMLAttributes& a)
|
|
|
|
{
|
|
|
|
if(naIsNil(_start_element)) return;
|
|
|
|
naRef attr = naNewHash(_c);
|
|
|
|
for(int i=0; i<a.size(); i++) {
|
|
|
|
naRef name = make_string(a.getName(i));
|
|
|
|
naRef value = make_string(a.getValue(i));
|
|
|
|
naHash_set(attr, name, value);
|
|
|
|
}
|
|
|
|
call(_start_element, 2, make_string(tag), attr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::endElement(const char* tag)
|
|
|
|
{
|
|
|
|
if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::data(const char* str, int len)
|
|
|
|
{
|
|
|
|
if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
|
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::pi(const char* target, const char* data)
|
|
|
|
{
|
2007-09-30 11:56:21 +00:00
|
|
|
if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(data));
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NasalXMLVisitor::call(naRef func, int num, naRef a, naRef b)
|
|
|
|
{
|
2007-06-30 09:44:33 +00:00
|
|
|
naRef args[2];
|
|
|
|
args[0] = a;
|
|
|
|
args[1] = b;
|
|
|
|
naCall(_c, func, num, args, naNil(), naNil());
|
add parsexml() function, which is a wrapper around the built-in easyxml
parser. Advantages over xml.nas: (reviewed and OK'ed by Andy)
- faster (33% ... only. I had hoped for more.)
- more standards compliant
- should support UTF
- I don't have to support it. ;-)
Usage: parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
<path> is an absolute file path, the rest are optional callback functions.
Example:
parsexml("/tmp/foo.xml", nil, nil, func(d) { print("DATA FOUND: ", d) });
2007-06-29 15:34:38 +00:00
|
|
|
if(naGetError(_c))
|
|
|
|
naRethrowError(_c);
|
|
|
|
}
|
|
|
|
|
|
|
|
naRef NasalXMLVisitor::make_string(const char* s, int n)
|
|
|
|
{
|
|
|
|
return naStr_fromdata(naNewString(_c), const_cast<char *>(s),
|
|
|
|
n < 0 ? strlen(s) : n);
|
|
|
|
}
|
|
|
|
|
|
|
|
|