#include <fstream>
#include <sstream>
-#include <plib/ul.h>
-
#include <simgear/nasal/nasal.h>
#include <simgear/props/props.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/interpolator.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/structure/commands.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
#include <Scenery/scenery.hxx>
-#include <Navaids/navrecord.hxx>
+#include <Navaids/navlist.hxx>
+#include <Navaids/procedure.hxx>
+
#include "NasalSys.hxx"
static FGNasalSys* nasalSys = 0;
+// Listener class for loading Nasal modules on demand
+class FGNasalModuleListener : public SGPropertyChangeListener
+{
+public:
+ FGNasalModuleListener(SGPropertyNode* node);
+
+ virtual void valueChanged(SGPropertyNode* node);
+
+private:
+ SGPropertyNode_ptr _node;
+};
+
+FGNasalModuleListener::FGNasalModuleListener(SGPropertyNode* node) : _node(node)
+{
+}
+
+void FGNasalModuleListener::valueChanged(SGPropertyNode*)
+{
+ if (_node->getBoolValue("enabled",false)&&
+ !_node->getBoolValue("loaded",true))
+ {
+ nasalSys->loadPropertyScripts(_node);
+ }
+}
+
// 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
case props::BOOL: case props::INT:
case props::LONG: case props::FLOAT:
case props::DOUBLE:
- return naNum(p->getDoubleValue());
-
+ {
+ double dv = p->getDoubleValue();
+ if (osg::isNaN(dv)) {
+ SG_LOG(SG_NASAL, SG_ALERT, "Nasal getprop: property " << p->getPath() << " is NaN");
+ return naNil();
+ }
+
+ return naNum(dv);
+ }
+
case props::STRING:
case props::UNSPECIFIED:
{
naRef n = naNumValue(val);
if(naIsNil(n))
naRuntimeError(c, "setprop() value is not string or number");
+
+ if (osg::isNaN(n.num)) {
+ naRuntimeError(c, "setprop() passed a NaN");
+ }
+
result = props->setDoubleValue(buf, n.num);
}
} catch (const string& err) {
if(naIsNil(s)) continue;
buf += naStr_data(s);
}
- SG_LOG(SG_GENERAL, SG_ALERT, buf);
+ SG_LOG(SG_NASAL, SG_ALERT, buf);
return naNum(buf.length());
}
{
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();
+
+ simgear::Dir d(SGPath(naStr_data(args[0])));
+ if(!d.exists()) 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);
+
+ simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
+ for (unsigned int i=0; i<paths.size(); ++i) {
+ std::string p = paths[i].file();
+ naVec_append(result, naStr_fromdata(naNewString(c), p.c_str(), p.size()));
+ }
+
return result;
}
// 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);
+ gettimeofday(&td, 0);
+ return naNum(td.tv_sec + 1e-6 * td.tv_usec);
#endif
}
HASHSET("ils_frequency_mhz", 17, naNum(rwy->ILS()->get_freq() / 100.0));
}
+ std::vector<flightgear::SID*> sids(rwy->getSIDs());
+ naRef sidVec = naNewVector(c);
+
+ for (unsigned int s=0; s < sids.size(); ++s) {
+ naRef procId = naStr_fromdata(naNewString(c),
+ const_cast<char *>(sids[s]->ident().c_str()),
+ sids[s]->ident().length());
+ naVec_append(sidVec, procId);
+ }
+ HASHSET("sids", 4, sidVec);
+
+ std::vector<flightgear::STAR*> stars(rwy->getSTARs());
+ naRef starVec = naNewVector(c);
+
+ for (unsigned int s=0; s < stars.size(); ++s) {
+ naRef procId = naStr_fromdata(naNewString(c),
+ const_cast<char *>(stars[s]->ident().c_str()),
+ stars[s]->ident().length());
+ naVec_append(starVec, procId);
+ }
+ HASHSET("stars", 5, starVec);
+
#undef HASHSET
naHash_set(rwys, rwyid, rwydata);
}
}
+// Returns vector of data hash for navaid of a <type>, nil on error
+// navaids sorted by ascending distance
+// navinfo([<lat>,<lon>],[<type>],[<id>])
+// lat/lon (numeric): use latitude/longitude instead of ac position
+// type: ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
+// id: (partial) id of the fix
+// examples:
+// navinfo("vor") returns all vors
+// navinfo("HAM") return all navaids who's name start with "HAM"
+// navinfo("vor", "HAM") return all vor who's name start with "HAM"
+//navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM"
+// sorted by distance relative to lat=34, lon=48
+static naRef f_navinfo(naContext c, naRef me, int argc, naRef* args)
+{
+ static SGConstPropertyNode_ptr latn = fgGetNode("/position/latitude-deg", true);
+ static SGConstPropertyNode_ptr lonn = fgGetNode("/position/longitude-deg", true);
+ SGGeod pos;
+
+ if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
+ pos = SGGeod::fromDeg(args[1].num, args[0].num);
+ args += 2;
+ argc -= 2;
+ } else {
+ pos = SGGeod::fromDeg(lonn->getDoubleValue(), latn->getDoubleValue());
+ }
+
+ FGPositioned::Type type = FGPositioned::INVALID;
+ nav_list_type navlist;
+ const char * id = "";
+
+ if(argc > 0 && naIsString(args[0])) {
+ const char *s = naStr_data(args[0]);
+ if(!strcmp(s, "any")) type = FGPositioned::INVALID;
+ else if(!strcmp(s, "fix")) type = FGPositioned::FIX;
+ else if(!strcmp(s, "vor")) type = FGPositioned::VOR;
+ else if(!strcmp(s, "ndb")) type = FGPositioned::NDB;
+ else if(!strcmp(s, "ils")) type = FGPositioned::ILS;
+ else if(!strcmp(s, "dme")) type = FGPositioned::DME;
+ else if(!strcmp(s, "tacan")) type = FGPositioned::TACAN;
+ else id = s; // this is an id
+ ++args;
+ --argc;
+ }
+
+ if(argc > 0 && naIsString(args[0])) {
+ if( *id != 0 ) {
+ naRuntimeError(c, "navinfo() called with navaid id");
+ return naNil();
+ }
+ id = naStr_data(args[0]);
+ ++args;
+ --argc;
+ }
+
+ if( argc > 0 ) {
+ naRuntimeError(c, "navinfo() called with too many arguments");
+ return naNil();
+ }
+
+ navlist = globals->get_navlist()->findByIdentAndFreq( pos, id, 0.0, type );
+
+ naRef reply = naNewVector(c);
+ for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
+ const FGNavRecord * nav = *it;
+
+ // set navdata hash
+ naRef navdata = naNewHash(c);
+#define HASHSET(s,l,n) naHash_set(navdata, naStr_fromdata(naNewString(c),s,l),n)
+ HASHSET("id", 2, naStr_fromdata(naNewString(c),
+ const_cast<char *>(nav->ident().c_str()), nav->ident().length()));
+ HASHSET("name", 4, naStr_fromdata(naNewString(c),
+ const_cast<char *>(nav->name().c_str()), nav->name().length()));
+ HASHSET("frequency", 9, naNum(nav->get_freq()));
+ HASHSET("lat", 3, naNum(nav->get_lat()));
+ HASHSET("lon", 3, naNum(nav->get_lon()));
+ HASHSET("elevation", 9, naNum(nav->get_elev_ft() * SG_FEET_TO_METER));
+ HASHSET("type", 4, naStr_fromdata(naNewString(c),
+ const_cast<char *>(nav->nameForType(nav->type())), strlen(nav->nameForType(nav->type()))));
+ HASHSET("distance", 8, naNum(SGGeodesy::distanceNm( pos, nav->geod() ) * SG_NM_TO_METER ) );
+ HASHSET("bearing", 7, naNum(SGGeodesy::courseDeg( pos, nav->geod() ) ) );
+#undef HASHSET
+ naVec_append( reply, navdata );
+ }
+ return reply;
+}
+
// Table of extension functions. Terminate with zeros.
static struct { const char* name; naCFunction func; } funcs[] = {
{ "getprop", f_getprop },
{ "geodtocart", f_geodtocart },
{ "geodinfo", f_geodinfo },
{ "airportinfo", f_airportinfo },
+ { "navinfo", f_navinfo },
{ 0, 0 }
};
hashset(_globals, "__gcsave", _gcHash);
// 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;
- loadModule(fullpath, file.base().c_str());
+ simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
+ loadScriptDirectory(nasalDir);
+
+ // Add modules in Nasal subdirectories to property tree
+ simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
+ simgear::Dir::NO_DOT_OR_DOTDOT, "");
+ for (unsigned int i=0; i<directories.size(); ++i) {
+ simgear::Dir dir(directories[i]);
+ simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
+ addModule(directories[i].file(), scripts);
}
- ulCloseDir(dir);
// set signal and remove node to avoid restoring at reinit
const char *s = "nasal-dir-initialized";
_context = naNewContext();
}
+bool pathSortPredicate(const SGPath& p1, const SGPath& p2)
+{
+ return p1.file() < p2.file();
+}
+
+// Loads all scripts in given directory
+void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir)
+{
+ simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE, ".nas");
+ // sort scripts, avoid loading sequence effects due to file system's
+ // random directory order
+ std::sort(scripts.begin(), scripts.end(), pathSortPredicate);
+
+ for (unsigned int i=0; i<scripts.size(); ++i) {
+ SGPath fullpath(scripts[i]);
+ SGPath file = fullpath.file();
+ loadModule(fullpath, file.base().c_str());
+ }
+}
+
+// Create module with list of scripts
+void FGNasalSys::addModule(string moduleName, simgear::PathList scripts)
+{
+ if (scripts.size()>0)
+ {
+ SGPropertyNode* nasal = globals->get_props()->getNode("nasal");
+ SGPropertyNode* module_node = nasal->getChild(moduleName,0,true);
+ for (unsigned int i=0; i<scripts.size(); ++i) {
+ SGPropertyNode* pFileNode = module_node->getChild("file",i,true);
+ pFileNode->setStringValue(scripts[i].c_str());
+ }
+ if (!module_node->hasChild("enabled",0))
+ {
+ SGPropertyNode* node = module_node->getChild("enabled",0,true);
+ node->setBoolValue(true);
+ node->setAttribute(SGPropertyNode::USERARCHIVE,true);
+ }
+ }
+}
+
// 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++) {
+ for(int i=0; i<nasal->nChildren(); i++)
+ {
SGPropertyNode* n = nasal->getChild(i);
+ loadPropertyScripts(n);
+ }
+}
- const char* module = n->getName();
- if(n->hasChild("module"))
- module = n->getStringValue("module");
+// Loads the scripts found under /nasal in the global tree
+void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
+{
+ bool is_loaded = false;
+ const char* module = n->getName();
+ if(n->hasChild("module"))
+ module = n->getStringValue("module");
+ if (n->getBoolValue("enabled",true))
+ {
// allow multiple files to be specified within a single
// Nasal module tag
int j = 0;
SGPropertyNode *fn;
bool file_specified = false;
+ bool ok=true;
while((fn = n->getChild("file", j)) != NULL) {
file_specified = true;
const char* file = fn->getStringValue();
- SGPath p = globals->resolve_maybe_aircraft_path(file);
- loadModule(p, module);
+ SGPath p(file);
+ if (!p.isAbsolute() || !p.exists())
+ {
+ p = globals->resolve_maybe_aircraft_path(file);
+ if (p.isNull())
+ {
+ SG_LOG(SG_NASAL, SG_ALERT, "Cannot find Nasal script '" <<
+ file << "' for module '" << module << "'.");
+ }
+ }
+ ok &= p.isNull() ? false : loadModule(p, module);
j++;
}
createModule(module, n->getPath().c_str(), src, strlen(src));
if(!file_specified && !src)
+ {
+ // module no longer exists - clear the archived "enable" flag
+ n->setAttribute(SGPropertyNode::USERARCHIVE,false);
+ SGPropertyNode* node = n->getChild("enabled",0,false);
+ if (node)
+ node->setAttribute(SGPropertyNode::USERARCHIVE,false);
+
SG_LOG(SG_NASAL, SG_ALERT, "Nasal error: " <<
- "no <file> or <script> defined in " <<
- "/nasal/" << module);
+ "no <file> or <script> defined in " <<
+ "/nasal/" << module);
+ }
+ else
+ is_loaded = ok;
+ }
+ else
+ {
+ SGPropertyNode* enable = n->getChild("enabled");
+ if (enable)
+ {
+ FGNasalModuleListener* listener = new FGNasalModuleListener(n);
+ enable->addChangeListener(listener, false);
+ }
}
+ SGPropertyNode* loaded = n->getChild("loaded",0,true);
+ loaded->setAttribute(SGPropertyNode::PRESERVE,true);
+ loaded->setBoolValue(is_loaded);
}
// Logs a runtime error, with stack trace, to the FlightGear log stream
// Reads a script file, executes it, and places the resulting
// namespace into the global namespace under the specified module
// name.
-void FGNasalSys::loadModule(SGPath file, const char* module)
+bool FGNasalSys::loadModule(SGPath file, const char* module)
{
int len = 0;
char* buf = readfile(file.c_str(), &len);
SG_LOG(SG_NASAL, SG_ALERT,
"Nasal error: could not read script file " << file.c_str()
<< " into module " << module);
- return;
+ return false;
}
- createModule(module, file.c_str(), buf, len);
+ bool ok = createModule(module, file.c_str(), buf, len);
delete[] buf;
+ return ok;
}
// Parse and run. Save the local variables namespace, as it will
// 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.)
-void FGNasalSys::createModule(const char* moduleName, const char* fileName,
+bool FGNasalSys::createModule(const char* moduleName, const char* fileName,
const char* src, int len,
const SGPropertyNode* cmdarg,
int argc, naRef* args)
{
naRef code = parse(fileName, src, len);
if(naIsNil(code))
- return;
+ return false;
// See if we already have a module hash to use. This allows the
// user to, for example, add functions to the built-in math
call(code, argc, args, locals);
hashset(_globals, moduleName, locals);
+ return true;
}
void FGNasalSys::deleteModule(const char* moduleName)
FGNasalListener *nl = new FGNasalListener(node, code, this,
gcSave(code), _listenerId, init, type);
- node->addChangeListener(nl, init);
+ node->addChangeListener(nl, init != 0);
_listener[_listenerId] = nl;
return naNum(_listenerId++);