37#include <simgear/debug/ErrorReportingCallback.hxx>
38#include <simgear/io/iostreams/sgstream.hxx>
39#include <simgear/math/sg_geodesy.hxx>
40#include <simgear/math/sg_random.hxx>
41#include <simgear/misc/sg_dir.hxx>
42#include <simgear/misc/sg_path.hxx>
43#include <simgear/misc/strutils.hxx>
44#include <simgear/nasal/iolib.h>
45#include <simgear/nasal/nasal.h>
46#include <simgear/props/props.hxx>
47#include <simgear/structure/commands.hxx>
48#include <simgear/structure/event_mgr.hxx>
49#include <simgear/io/sg_mmap.hxx>
51#include <simgear/nasal/cppbind/from_nasal.hxx>
52#include <simgear/nasal/cppbind/to_nasal.hxx>
53#include <simgear/nasal/cppbind/Ghost.hxx>
54#include <simgear/nasal/cppbind/NasalHash.hxx>
55#include <simgear/timing/timestamp.hxx>
98 SGPropertyNode_ptr _node;
107 const auto enabled = _node->getBoolValue(
"enabled",
false);
108 const auto loaded = _node->getBoolValue(
"loaded",
true);
109 if (enabled && !loaded) {
110 nasalSys->loadPropertyScripts(_node);
111 }
else if (!enabled && loaded) {
113 std::string module = _node->getNameString();
114 if (_node->hasChild(
"module")) {
115 module = _node->getStringValue("module");
118 nasalSys->deleteModule(module.c_str());
135 snprintf(nm, 128,
"maketimer-[%p]-%s:%d", (
void*)
this, naStr_data(naGetSourceFile(c, 0)), naGetLine(c, 0));
138 snprintf(nm, 128,
"maketimer-%p",
this);
141 _gcRoot = naGCSave(f);
142 _gcSelf = naGCSave(self);
143 sys->addPersistentTimer(
this);
149 naGCRelease(_gcRoot);
150 naGCRelease(_gcSelf);
151 _sys->removePersistentTimer(
this);
159 globals->get_event_mgr()->removeTask(_name);
169 SG_LOG(SG_NASAL, SG_WARN,
"can't change type of running timer!");
184 globals->get_event_mgr()->addEvent(_name, [
this](){ this->
invoke(); }, _interval, _isSimTime);
186 globals->get_event_mgr()->addTask(_name, [
this](){ this->
invoke(); },
187 _interval, _interval ,
195 _interval = newInterval;
208 naRef *args =
nullptr;
209 _sys->callMethod(_func, _self, 0, args, naNil() );
214 _singleShot = aSingleShot;
218 {
return _singleShot; }
220 const std::string&
name()
const
226 int _gcRoot, _gcSelf;
227 bool _isRunning =
false;
229 bool _singleShot =
false;
230 bool _isSimTime =
false;
239 naRuntimeError(c,
"Timer is running, cannot change type between real/sim time");
243 timer.
setSimTime(nasal::from_nasal<bool>(c, value));
265 return timestamp.elapsedMSec();
269 return timestamp.elapsedUSec();
272 SGTimeStamp timestamp;
289 d->_globals = naNil();
290 d->_string = naNil();
291 d->_wrappedNodeFunc = naNil();
293 d->_log.reset(
new simgear::BufferedLogCallback(SG_NASAL, SG_INFO));
294 d->_log->truncateAt(255);
295 sglog().addCallback(d->_log.get());
297 naSetErrorHandler(&logError);
304 naRef s = naNewString(d->_context);
305 naStr_fromdata(s, (
char*)key, strlen(key));
306 naHash_set(hash, s, val);
311 hashset(d->_globals, key, val);
316 return callMethod(code, naNil(), argc, args, locals);
334 return naCallMethod(code, self, argc, args, locals);
335 }
catch (sg_exception& e) {
336 SG_LOG(SG_NASAL, SG_DEV_ALERT,
"caught exception invoking nasal method:" << e.what());
344 return naCallMethodCtx(ctx, code, self, argc, args, locals);
345 }
catch (sg_exception& e) {
346 SG_LOG(SG_NASAL, SG_DEV_ALERT,
"caught exception invoking nasal method:" << e.what());
348 logNasalStack(ctx, nasalStack);
357 SG_LOG(SG_GENERAL, SG_ALERT,
"Nasal was not shutdown");
359 sglog().removeCallback(d->_log.get());
366 naContext ctx = naNewContext();
367 naRef code = parse(ctx,
"FGNasalSys::parseAndRun()", source.c_str(),
368 source.size(), errors);
377 if (!naIsNil(result)) {
378 naRef s = naStringValue(ctx, result);
380 output = naStr_data(s);
396FGNasalScript* FGNasalSys::parseScript(
const char* src,
const char*
name)
398 FGNasalScript* script =
new FGNasalScript();
404 snprintf(buf, 256,
"FGNasalScript@%p", (
void *)script);
408 script->_code = parse(
name, src, strlen(src));
409 if(naIsNil(script->_code)) {
414 script->_gcKey =
gcSave(script->_code);
424static SGPropertyNode*
findnode(naContext c, naRef* vec,
int len,
bool create=
false)
426 SGPropertyNode*
p =
globals->get_props();
428 for(
int i=0;
i<len;
i++) {
431 naRuntimeError(c,
"bad argument to setprop/getprop path: expected a string");
433 naRef b =
i < len-1 ? naNumValue(vec[
i+1]) : naNil();
435 p =
p->getNode(naStr_data(a), (
int)b.num, create);
438 p =
p->getNode(naStr_data(a), create);
442 }
catch (
const string& err) {
443 naRuntimeError(c, (
char *)err.c_str());
451static naRef
f_getprop(naContext c, naRef me,
int argc, naRef* args)
455 naRuntimeError(c,
"getprop() expects at least 1 argument");
457 const SGPropertyNode*
p =
findnode(c, args, argc,
false);
458 if(!
p)
return naNil();
460 switch(
p->getType()) {
461 case props::BOOL:
case props::INT:
462 case props::LONG:
case props::FLOAT:
465 double dv =
p->getDoubleValue();
466 if (SGMisc<double>::isNaN(dv)) {
467 SG_LOG(SG_NASAL, SG_ALERT,
"Nasal getprop: property " <<
p->getPath() <<
" is NaN");
475 case props::UNSPECIFIED:
477 naRef nastr = naNewString(c);
478 std::string val =
p->getStringValue();
479 naStr_fromdata(nastr, val.c_str(), val.length());
491static naRef
f_setprop(naContext c, naRef me,
int argc, naRef* args)
494 naRuntimeError(c,
"setprop() expects at least 2 arguments");
496 naRef val = args[argc - 1];
497 SGPropertyNode*
p =
findnode(c, args, argc-1,
true);
501 if(naIsString(val)) result =
p->setStringValue(naStr_data(val));
504 naRuntimeError(c,
"setprop() value is not string or number");
506 if (SGMisc<double>::isNaN(val.num)) {
507 naRuntimeError(c,
"setprop() passed a NaN");
510 result =
p->setDoubleValue(val.num);
512 }
catch (
const string& err) {
513 naRuntimeError(c, (
char *)err.c_str());
515 return naNum(result);
522 SGPropertyNode* node =
fgGetNode(
"/sim/nasal-log-file-line",
true );
523 node->addChangeListener(
this,
true );
538static naRef
f_print(naContext c, naRef me,
int argc, naRef* args)
543 for(
int i=0;
i<n;
i++) {
544 naRef s = naStringValue(c, args[
i]);
545 if(naIsNil(s))
continue;
546 buf += naStr_data(s);
553 const char* file = naStr_data(naGetSourceFile(c, 0));
554 if (simgear::strutils::ends_with( file,
"/globals.nas")) {
560 file = naStr_data(naGetSourceFile(c, frame));
562 int line = naGetLine(c, frame);
563 const char* function =
"";
564 if (sglog().would_log(SG_NASAL, SG_MANDATORY_INFO, file, line, function)) {
565 sglog().logCopyingFilename(SG_NASAL, SG_MANDATORY_INFO, file, line, function, buf);
568 SG_LOG(SG_NASAL, SG_MANDATORY_INFO, buf);
570 return naNum(buf.length());
576static naRef
f_logprint(naContext c, naRef me,
int argc, naRef* args)
579 naRuntimeError(c,
"no prioirty argument to logprint()");
581 naRef priority = args[0];
584 for(
int i=1;
i<n;
i++) {
585 naRef s = naStringValue(c, args[
i]);
586 if(naIsNil(s))
continue;
587 buf += naStr_data(s);
591 sglog().logCopyingFilename(SG_NASAL, (sgDebugPriority)(
int) priority.num,
592 naStr_data(naGetSourceFile(c, 0)),
593 naGetLine(c, 0),
"" , buf);
594 return naNum(buf.length());
601static naRef
f_fgcommand(naContext c, naRef me,
int argc, naRef* args)
603 naRef cmd = argc > 0 ? args[0] : naNil();
604 naRef props = argc > 1 ? args[1] : naNil();
605 if(!naIsString(cmd) || (!naIsNil(props) && !naIsGhost(props)))
606 naRuntimeError(c,
"bad arguments to fgcommand()");
607 SGPropertyNode_ptr node;
609 node =
static_cast<SGPropertyNode*
>(naGhost_ptr(props));
611 node =
new SGPropertyNode;
613 return naNum(
globals->get_commands()->execute(naStr_data(cmd), node,
nullptr));
618static naRef
f_settimer(naContext c, naRef me,
int argc, naRef* args)
624static naRef
f_makeTimer(naContext c, naRef me,
int argc, naRef* args)
626 if (!naIsNum(args[0])) {
627 naRuntimeError(c,
"bad interval argument to maketimer");
630 naRef
func, self = naNil();
631 if (naIsFunc(args[1])) {
633 }
else if ((argc == 3) && naIsFunc(args[2])) {
637 naRuntimeError(c,
"bad function/self arguments to maketimer");
641 return nasal::to_nasal(c, timerObj);
646 if (!naIsNum(args[0])) {
647 naRuntimeError(c,
"bad interval argument to makesingleshot");
650 naRef
func, self = naNil();
651 if (naIsFunc(args[1])) {
653 }
else if ((argc == 3) && naIsFunc(args[2])) {
657 naRuntimeError(c,
"bad function/self arguments to makesingleshot");
661 timerObj->setSingleShot(
true);
663 return nasal::to_nasal(c, timerObj);
669 return nasal::to_nasal(c, timeStampObj);
676 return nasalSys->setListener(c, argc, args);
683 return nasalSys->removeListener(c, argc, args);
688static naRef
f_cmdarg(naContext c, naRef me,
int argc, naRef* args)
699 SGPropertyNode* node;
700 naRef prop = argc > 0 ? args[0] : naNil();
701 if(naIsString(prop)) node =
fgGetNode(naStr_data(prop),
true);
702 else if(naIsGhost(prop)) node =
static_cast<SGPropertyNode*
>(naGhost_ptr(prop));
705 naRef curve = argc > 1 ? args[1] : naNil();
706 if(!naIsVector(curve))
return naNil();
707 int nPoints = naVec_size(curve) / 2;
709 simgear::PropertyList value_nodes;
710 value_nodes.reserve(nPoints);
711 std::vector<double> deltas;
712 deltas.reserve(nPoints);
714 for(
int i = 0;
i < nPoints; ++
i )
716 SGPropertyNode* val =
new SGPropertyNode;
717 val->setDoubleValue(naNumValue(naVec_get(curve, 2*
i)).num);
718 value_nodes.push_back(val);
719 deltas.push_back(naNumValue(naVec_get(curve, 2*
i+1)).num);
722 node->interpolate(
"numeric", value_nodes, deltas,
"linear");
729static naRef
f_rand(naContext c, naRef me,
int argc, naRef* args)
731 return naNum(sg_random());
734static naRef
f_srand(naContext c, naRef me,
int argc, naRef* args)
740static naRef
f_abort(naContext c, naRef me,
int argc, naRef* args)
747static naRef
f_directory(naContext c, naRef me,
int argc, naRef* args)
749 if(argc != 1 || !naIsString(args[0]))
750 naRuntimeError(c,
"bad arguments to directory()");
752 SGPath dirname = SGPath::fromUtf8(naStr_data(args[0])).validate(
false);
753 if(dirname.isNull()) {
754 SG_LOG(SG_NASAL, SG_ALERT,
"directory(): listing '" <<
755 naStr_data(args[0]) <<
"' denied (unauthorized directory - authorization"
756 " no longer follows symlinks; to authorize reading additional "
757 "directories, pass them to --allow-nasal-read)");
762 simgear::Dir d(dirname);
763 if(!d.exists())
return naNil();
764 naRef result = naNewVector(c);
766 simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE | simgear::Dir::TYPE_DIR);
767 for (
unsigned int i=0;
i<paths.size(); ++
i) {
768 std::string
p = paths[
i].file();
769 naVec_append(result, naStr_fromdata(naNewString(c),
p.c_str(),
p.size()));
780 if(argc != 1 || !naIsString(args[0]))
781 naRuntimeError(c,
"bad arguments to resolveDataPath()");
783 SGPath
p =
globals->resolve_maybe_aircraft_path(naStr_data(args[0]));
784 std::string pdata =
p.utf8Str();
785 return naStr_fromdata(naNewString(c),
const_cast<char*
>(pdata.c_str()), pdata.length());
790 if(argc != 1 || !naIsString(args[0]))
791 naRuntimeError(c,
"bad arguments to findDataDir()");
793 SGPath
p =
globals->findDataPath(naStr_data(args[0]));
794 std::string pdata =
p.utf8Str();
795 return naStr_fromdata(naNewString(c),
const_cast<char*
>(pdata.c_str()), pdata.length());
806 globals->get_commands()->addCommandObject(_name,
this);
812 _sys->gcRelease(_gcRoot);
815 bool operator()(
const SGPropertyNode* aNode, SGPropertyNode* root)
override
817 _sys->setCmdArg(
const_cast<SGPropertyNode*
>(aNode));
819 args[0] = _sys->wrappedPropsNode(
const_cast<SGPropertyNode*
>(aNode));
821 _sys->callMethod(_func, naNil(), 1, args, naNil() );
835 if(argc != 2 || !naIsString(args[0]) || !naIsFunc(args[1]))
836 naRuntimeError(c,
"bad arguments to addcommand()");
838 const string commandName(naStr_data(args[0]));
839 bool ok =
nasalSys->addCommand(args[1], commandName);
841 naRuntimeError(c,
"Failed to add command:%s : likely a duplicate name ", commandName.c_str());
849 if ((argc < 1) || !naIsString(args[0]))
850 naRuntimeError(c,
"bad argument to removecommand()");
852 const string commandName(naStr_data(args[0]));
853 bool ok =
nasalSys->removeCommand(commandName);
860static naRef
f_open(naContext c, naRef me,
int argc, naRef* args)
863 naRef file = argc > 0 ? naStringValue(c, args[0]) : naNil();
864 naRef mode = argc > 1 ? naStringValue(c, args[1]) : naNil();
865 if(!naStr_data(file)) naRuntimeError(c,
"bad argument to open()");
866 const char* modestr = naStr_data(mode) ? naStr_data(mode) :
"rb";
867 const SGPath filename = SGPath::fromUtf8(naStr_data(file)).validate(
868 strcmp(modestr,
"rb") && strcmp(modestr,
"r"));
869 if(filename.isNull()) {
870 SG_LOG(SG_NASAL, SG_ALERT,
"open(): reading/writing '" <<
871 naStr_data(file) <<
"' denied (unauthorized directory - authorization"
872 " no longer follows symlinks; to authorize reading additional "
873 "directories, pass them to --allow-nasal-read)");
874 naRuntimeError(c,
"open(): access denied (unauthorized directory)");
878#if defined(SG_WINDOWS)
879 std::wstring fp = filename.wstr();
880 std::wstring wmodestr = simgear::strutils::convertUtf8ToWString(modestr);
881 f = _wfopen(fp.c_str(), wmodestr.c_str());
883 std::string fp = filename.utf8Str();
884 f = fopen(fp.c_str(), modestr);
886 if(!f) naRuntimeError(c, strerror(errno));
887 return naIOGhost(c, f);
890static naRef
ftype(naContext ctx,
const SGPath& f)
892 const char* t =
"unk";
893 if (f.isFile()) t =
"reg";
894 else if(f.isDir()) t =
"dir";
895 return naStr_fromdata(naNewString(ctx), t, strlen(t));
902 naRef pathArg = argc > 0 ? naStringValue(ctx, args[0]) : naNil();
903 if (!naIsString(pathArg))
904 naRuntimeError(ctx,
"bad argument to stat()");
906 const auto path = SGPath::fromUtf8(naStr_data(pathArg));
907 if (!path.exists()) {
911 const SGPath filename = SGPath(path).validate(
false);
912 if (filename.isNull()) {
913 SG_LOG(SG_NASAL, SG_ALERT,
"stat(): reading '" <<
914 naStr_data(pathArg) <<
"' denied (unauthorized directory - authorization"
915 " no longer follows symlinks; to authorize reading additional "
916 "directories, pass them to --allow-nasal-read)");
917 naRuntimeError(ctx,
"stat(): access denied (unauthorized directory)");
921 naRef result = naNewVector(ctx);
922 naVec_setsize(ctx, result, 12);
927 naVec_set(result, n++, naNum(0));
928 naVec_set(result, n++, naNum(0));
929 naVec_set(result, n++, naNum(0));
930 naVec_set(result, n++, naNum(0));
931 naVec_set(result, n++, naNum(0));
932 naVec_set(result, n++, naNum(0));
933 naVec_set(result, n++, naNum(0));
934 naVec_set(result, n++, naNum(filename.sizeInBytes()));
935 naVec_set(result, n++, naNum(0));
936 naVec_set(result, n++, naNum(0));
937 naVec_set(result, n++, naNum(0));
938 naVec_set(result, n++,
ftype(ctx, filename));
953static naRef
f_parsexml(naContext c, naRef me,
int argc, naRef* args)
955 if(argc < 1 || !naIsString(args[0]))
956 naRuntimeError(c,
"parsexml(): path argument missing or not a string");
957 if(argc > 5) argc = 5;
958 for(
int i=1;
i<argc;
i++)
959 if(!(naIsNil(args[
i]) || naIsFunc(args[
i])))
960 naRuntimeError(c,
"parsexml(): callback argument not a function");
962 const SGPath file = SGPath::fromUtf8(naStr_data(args[0])).validate(
false);
964 SG_LOG(SG_NASAL, SG_ALERT,
"parsexml(): reading '" <<
965 naStr_data(args[0]) <<
"' denied (unauthorized directory - authorization"
966 " no longer follows symlinks; to authorize reading additional "
967 "directories, pass them to --allow-nasal-read)");
968 naRuntimeError(c,
"parsexml(): access denied (unauthorized directory)");
974 readXML(file, visitor);
975 }
catch (
const sg_exception& e) {
976 std::string fp = file.utf8Str();
977 naRuntimeError(c,
"parsexml(): file '%s' %s", fp.c_str(), e.getFormattedMessage().c_str());
981 std::string fs = file.utf8Str();
982 return naStr_fromdata(naNewString(c), fs.c_str(), fs.length());
990static naRef
f_md5(naContext c, naRef me,
int argc, naRef* args)
992 if( argc != 1 || !naIsString(args[0]) )
993 naRuntimeError(c,
"md5(): wrong type or number of arguments");
995 return nasal::to_nasal(
997 simgear::strutils::md5(naStr_data(args[0]), naStr_len(args[0]))
1002static naRef
f_systime(naContext c, naRef me,
int argc, naRef* args)
1006 GetSystemTimeAsFileTime(&ft);
1007 double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
1009 return naNum((t * 1e-7) - 11644473600.0);
1012 gettimeofday(&td, 0);
1013 return naNum(td.tv_sec + 1e-6 * td.tv_usec);
1051void FGNasalSys::initLogLevelConstants()
1053 hashset(d->_globals,
"LOG_BULK", naNum(SG_BULK));
1054 hashset(d->_globals,
"LOG_WARN", naNum(SG_WARN));
1055 hashset(d->_globals,
"LOG_DEBUG", naNum(SG_DEBUG));
1056 hashset(d->_globals,
"LOG_INFO", naNum(SG_INFO));
1057 hashset(d->_globals,
"LOG_ALERT", naNum(SG_ALERT));
1058 hashset(d->_globals,
"DEV_WARN", naNum(SG_DEV_WARN));
1059 hashset(d->_globals,
"DEV_ALERT", naNum(SG_DEV_ALERT));
1060 hashset(d->_globals,
"MANDATORY_INFO", naNum(SG_MANDATORY_INFO));
1071 SG_LOG(SG_GENERAL, SG_ALERT,
"duplicate init of Nasal");
1075 d->_context = naNewContext();
1081 d->_globals = naInit_std(d->_context);
1082 naSave(d->_context, d->_globals);
1083 hashset(d->_globals,
"globals", d->_globals);
1085 hashset(d->_globals,
"math", naInit_math(d->_context));
1086 hashset(d->_globals,
"bits", naInit_bits(d->_context));
1087 hashset(d->_globals,
"io", naInit_io(d->_context));
1088 hashset(d->_globals,
"thread", naInit_thread(d->_context));
1089 hashset(d->_globals,
"utf8", naInit_utf8(d->_context));
1092 initLogLevelConstants();
1097 naNewFunc(d->_context, naNewCCode(d->_context,
funcs[
i].func)));
1098 nasal::Hash io_module =
getGlobals().get<nasal::Hash>(
"io");
1099 io_module.set(
"open",
f_open);
1103 hashset(d->_globals,
"props", genPropsModule());
1106 d->_string = naInit_string(d->_context);
1107 naSave(d->_context, d->_string);
1110#if defined (BUILDING_TESTSUITE)
1122#if !defined (BUILDING_TESTSUITE)
1133 NasalTimerObj::init(
"Timer")
1141 NasalTimeStampObj::init(
"TimeStamp")
1158 simgear::Dir nasalDir(SGPath(
globals->get_fg_root(),
"Nasal"));
1162 loadScriptDirectory(nasalDir,
1163 globals->get_props()->getNode(
"/sim/nasal-load-priority"),
1167 simgear::PathList directories = nasalDir.children(simgear::Dir::TYPE_DIR+
1168 simgear::Dir::NO_DOT_OR_DOTDOT,
"");
1169 for (
unsigned int i=0;
i<directories.size(); ++
i) {
1170 simgear::Dir dir(directories[
i]);
1171 simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE,
".nas");
1172 addModule(directories[
i].file(), scripts);
1176 const char *s =
"nasal-dir-initialized";
1177 SGPropertyNode *signal =
fgGetNode(
"/sim/signals",
true);
1178 signal->setBoolValue(s,
true);
1179 signal->removeChildren(s);
1182 loadPropertyScripts();
1201 for (
auto l : d->_listener)
1203 d->_listener.clear();
1205 for (
auto c : d->_commands) {
1206 globals->get_commands()->removeCommand(c.first);
1208 d->_commands.clear();
1210 for (
auto ml : d->_moduleListeners)
1212 d->_moduleListeners.clear();
1214 for (
auto t : d->_nasalTimers) {
1217 d->_nasalTimers.clear();
1221 d->_string = naNil();
1222 naFreeContext(d->_context);
1227 hashset(d->_globals,
"globals", naNil());
1228 d->_globals = naNil();
1234 nasal::ghostProcessDestroyList();
1236 if (!d->_persistentTimers.empty()) {
1237 SG_LOG(SG_NASAL, SG_DEV_WARN,
"Extant persistent timer count:" << d->_persistentTimers.size());
1239 for (
auto pt : d->_persistentTimers) {
1240 SG_LOG(SG_NASAL, SG_DEV_WARN,
"Extant:" << pt <<
" : " << pt->name());
1249 if (naIsNil(d->_wrappedNodeFunc)) {
1250 nasal::Hash props =
getGlobals().get<nasal::Hash>(
"props");
1251 d->_wrappedNodeFunc = props.get(
"wrapNode");
1256 naContext ctx = naNewContext();
1257 naRef wrapped = naCallMethodCtx(ctx, d->_wrappedNodeFunc, naNil(), 1, args, naNil());
1267 std::for_each(d->_dead_listener.begin(), d->_dead_listener.end(),
1269 d->_dead_listener.clear();
1271 if (!d->_loadList.empty()) {
1273 d->_delay_load =
false;
1276 d->_loadList.pop()->load();
1277 }
else if (!d->_unloadList.empty()) {
1280 d->_unloadList.pop()->unload();
1283 nasal::ghostProcessDestroyList();
1295 naFreeContext(d->_context);
1296 d->_context = naNewContext();
1301 return p1.file() <
p2.file();
1306void FGNasalSys::loadScriptDirectory(simgear::Dir nasalDir, SGPropertyNode* loadorder,
1307 bool excludeUnspecifiedInLoadOrder)
1309 simgear::PathList scripts = nasalDir.children(simgear::Dir::TYPE_FILE,
".nas");
1311 if (loadorder !=
nullptr && loadorder->hasChild(
"file")) {
1314 simgear::PropertyList files = loadorder->getChildren(
"file");
1316 auto loadAndErase = [ &scripts, &nasalDir, this ] (SGPropertyNode_ptr n) {
1317 SGPath
p = SGPath(nasalDir.path(), n->getStringValue());
1318 auto script = std::find(scripts.begin(), scripts.end(),
p);
1319 if (script != scripts.end()) {
1321 scripts.erase(script);
1325 std::for_each(files.begin(), files.end(), loadAndErase);
1328 if (excludeUnspecifiedInLoadOrder) {
1335 std::for_each(scripts.begin(), scripts.end(), [
this](SGPath
p) { this->loadModule(p, p.file_base().c_str()); });
1339void FGNasalSys::addModule(
string moduleName, simgear::PathList scripts)
1341 if (! scripts.empty())
1344 SGPropertyNode* module_node = nasal->getChild(moduleName,0,
true);
1345 for (
unsigned int i=0;
i<scripts.size(); ++
i) {
1346 SGPropertyNode* pFileNode = module_node->getChild(
"file",
i,
true);
1347 pFileNode->setStringValue(scripts[
i].utf8Str());
1349 if (!module_node->hasChild(
"enabled",0))
1351 SGPropertyNode* node = module_node->getChild(
"enabled",0,
true);
1352 node->setBoolValue(
false);
1353 node->setAttribute(SGPropertyNode::USERARCHIVE,
false);
1354 #ifndef BUILDING_TESTSUITE
1355 SG_LOG(SG_NASAL, SG_ALERT,
"Nasal module " << moduleName
1356 <<
" present in FGDATA/Nasal but not configured in defaults.xml. "
1357 <<
" Please add an entry to defaults.xml, and set " << node->getPath()
1358 <<
"=true to load the module on-demand at runtime when required."
1366void FGNasalSys::loadPropertyScripts()
1371 for(
int i=0;
i<nasal->nChildren();
i++)
1373 SGPropertyNode* n = nasal->getChild(
i);
1374 loadPropertyScripts(n);
1379void FGNasalSys::loadPropertyScripts(SGPropertyNode* n)
1381 bool is_loaded =
false;
1383 std::string module = n->getNameString();
1384 if(n->hasChild(
"module"))
1385 module = n->getStringValue("module");
1386 if (n->getBoolValue(
"enabled",
true))
1392 bool file_specified =
false;
1394 while((fn = n->getChild(
"file", j)) != NULL) {
1395 file_specified =
true;
1396 std::string file = fn->getStringValue();
1398 if (!
p.isAbsolute() || !
p.exists())
1402 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AircraftSystems,
1403 string{
"Missing nasal file for module:"} + module, sg_location{file});
1406 ok &=
p.isNull() ? false :
loadModule(
p, module.c_str());
1410 std::string src = n->getStringValue(
"script");
1411 if(!n->hasChild(
"script")) src =
"";
1413 createModule(module.c_str(), n->getPath().c_str(), src.c_str(), src.length());
1415 if(!file_specified && src.empty())
1418 n->setAttribute(SGPropertyNode::USERARCHIVE,
false);
1419 SGPropertyNode* node = n->getChild(
"enabled",0,
false);
1421 node->setAttribute(SGPropertyNode::USERARCHIVE,
false);
1423 SG_LOG(SG_NASAL, SG_ALERT,
"Nasal error: " <<
1424 "no <file> or <script> defined in " <<
1425 "/nasal/" << module);
1432 SGPropertyNode* enable = n->getChild(
"enabled");
1435 FGNasalModuleListener* listener =
new FGNasalModuleListener(n);
1436 d->_moduleListeners.push_back(listener);
1437 enable->addChangeListener(listener,
false);
1440 SGPropertyNode* loaded = n->getChild(
"loaded",0,
true);
1441 loaded->setAttribute(SGPropertyNode::PRESERVE,
true);
1442 loaded->setBoolValue(is_loaded);
1445#if defined(BUILDING_TESTSUITE)
1452 global_nasalErrors.swap(r);
1458void FGNasalSys::logError(naContext context)
1460 string errorMessage = naGetError(context);
1461#if defined(BUILDING_TESTSUITE)
1462 global_nasalErrors.push_back(errorMessage);
1464 SG_LOG(SG_NASAL, SG_ALERT,
"Nasal runtime error: " << errorMessage);
1467 logNasalStack(context, nasalStack);
1473void FGNasalSys::logNasalStack(naContext context,
string_list& stack)
1475 const int stack_depth = naStackDepth(context);
1476 for (
int i=0;
i<stack_depth; ++
i) {
1477 std::string text = std::string(naStr_data(naGetSourceFile(context,
i)))
1478 +
", line " + std::to_string(naGetLine(context,
i));
1479 stack.push_back(text);
1480 SG_LOG(SG_NASAL, SG_ALERT, ((
i) ?
" called from: " :
" at ") << text);
1490 if (!file.exists()) {
1491 SG_LOG(SG_NASAL, SG_ALERT,
"Cannot load module, missing file:" << file);
1495 SGMMapFile mmap(file);
1496 mmap.open(SG_IO_IN);
1498 auto pathStr = file.utf8Str();
1499 return createModule(module, pathStr.c_str(), mmap.get(), mmap.get_size());
1507 const char* src,
int len,
1508 const SGPropertyNode* cmdarg,
1509 int argc, naRef* args)
1511 naContext ctx = naNewContext();
1513 naRef code = parse(ctx, fileName, src, len, errors);
1524 naRef modname = naNewString(ctx);
1525 naStr_fromdata(modname, (
char*)moduleName, strlen(moduleName));
1526 if (naIsNil(d->_globals))
1529 if (!naHash_get(d->_globals, modname, &locals)) {
1533 const auto isCanvas = strcmp(moduleName,
"canvas") == 0;
1536 naHash_get(d->_globals, modname, &locals);
1538 locals = naNewHash(ctx);
1546 naRef modFilePath = naNewString(ctx);
1547 naStr_fromdata(modFilePath, (
char*)fileName, strlen(fileName));
1548 hashset(locals,
"__moduleFilePath", modFilePath);
1550 d->_cmdArg = (SGPropertyNode*)cmdarg;
1552 hashset(d->_globals, moduleName, locals);
1560 if (!d->_inited || naIsNil(d->_globals)) {
1566 auto nasalNode =
globals->get_props()->getNode(
"nasal",
true);
1567 auto moduleNode = nasalNode->getChild(moduleName, 0);
1571 moduleNode->setBoolValue(
"loaded",
false);
1574 naContext ctx = naNewContext();
1575 naRef modname = naNewString(ctx);
1576 naStr_fromdata(modname, (
char*)moduleName, strlen(moduleName));
1579 if (naHash_get(d->_globals, modname, &locals)) {
1580 naRef unloadFunc = naHash_cget(locals, (
char*)
"unload");
1581 if (naIsFunc(unloadFunc)) {
1586 naHash_delete(d->_globals, modname);
1594 if (!d->_inited || naIsNil(d->_globals)) {
1598 naRef locals = naHash_cget(d->_globals, (
char*)moduleName.c_str());
1599 if (naIsNil(locals)) {
1608 auto nasalNode =
globals->get_props()->getNode(
"nasal",
true);
1609 auto moduleNode = nasalNode->getChild(moduleName, 0);
1612 loadPropertyScripts(moduleNode);
1616 naRef filePath = naHash_cget(locals, (
char*)
"__moduleFilePath");
1617 if (naIsNil(filePath)) {
1621 SGPath path = SGPath::fromUtf8(naStr_data(filePath));
1629 naRef mod = naHash_cget(d->_globals, (
char*)moduleName.c_str());
1635 naRef mod = naHash_cget(d->_globals, (
char*)moduleName);
1639naRef FGNasalSys::parse(naContext ctx,
const char* filename,
1640 const char* buf,
int len,
1641 std::string& errors)
1644 naRef srcfile = naNewString(ctx);
1645 naStr_fromdata(srcfile, (
char*)filename, strlen(filename));
1646 naRef code = naParseCode(ctx, srcfile, 1, (
char*)buf, len, &errLine);
1648 std::ostringstream errorMessageStream;
1649 errorMessageStream <<
"Nasal parse error: " << naGetError(ctx) <<
1650 " in "<< filename <<
", line " << errLine;
1651 errors = errorMessageStream.str();
1652 SG_LOG(SG_NASAL, SG_ALERT, errors);
1656 const char* line_begin = buf;
1657 const char* line_end =
nullptr;
1660 line_end = strchr(line_begin,
'\n');
1662 line_end = line_begin + strlen(line_begin);
1665 if (line_num == errLine)
break;
1666 line_begin = line_end + 1;
1669 if (line_num == errLine) {
1670 SG_LOG(SG_NASAL, SG_ALERT, std::string(line_begin, line_end) <<
"\n");
1673 SG_LOG(SG_NASAL, SG_ALERT,
"[Could not find line " << errLine <<
" - only " << line_num <<
" lines.");
1680 return naBindFunction(ctx, code, d->_globals);
1684 const char* fileName,
1686 const SGPropertyNode* arg,
1687 SGPropertyNode* root)
1689 naContext ctx = naNewContext();
1690 std::string errorMessage;
1691 naRef code = parse(ctx, fileName, src, strlen(src), errorMessage);
1700 naRef locals = naNil();
1702 naRef modname = naNewString(ctx);
1703 naStr_fromdata(modname, (
char*)moduleName, strlen(moduleName));
1704 if (!naHash_get(d->_globals, modname, &locals)) {
1705 locals = naNewHash(ctx);
1706 naHash_set(d->_globals, modname, locals);
1713 d->_cmdArg = (SGPropertyNode*)arg;
1722 std::string src = arg->getStringValue(
"script");
1723 std::string moduleName = arg->getStringValue(
"module");
1726 arg->getPath(
true).c_str(),
1745 naRef handler = argc > 0 ? args[0] : naNil();
1746 if(!(naIsCode(handler) || naIsCCode(handler) || naIsFunc(handler))) {
1747 naRuntimeError(c,
"settimer() with invalid function argument");
1751 naRef delta = argc > 1 ? args[1] : naNil();
1752 if(naIsNil(delta)) {
1753 naRuntimeError(c,
"settimer() with invalid time argument");
1757 bool simtime = (argc > 2 && naTrue(args[2])) ?
false :
true;
1760 std::string
name =
"settimer-";
1761 name.append(naStr_data(naGetSourceFile(c, 0)));
1763 name.append(std::to_string(naGetLine(c, 0)));
1766 NasalTimer* t =
new NasalTimer(handler,
this);
1767 d->_nasalTimers.push_back(t);
1770 delta.num, simtime);
1776 auto it = std::find(d->_nasalTimers.begin(), d->_nasalTimers.end(), t);
1777 assert(it != d->_nasalTimers.end());
1778 d->_nasalTimers.erase(it);
1809 nasal->handleTimer(
this);
1829 SGPropertyNode_ptr node;
1830 naRef prop = argc > 0 ? args[0] : naNil();
1831 if(naIsString(prop)) node =
fgGetNode(naStr_data(prop),
true);
1832 else if(naIsGhost(prop)) node =
static_cast<SGPropertyNode*
>(naGhost_ptr(prop));
1834 naRuntimeError(c,
"setlistener() with invalid property argument");
1838 if (node->isTied() || node->isAlias()) {
1839 const auto isSafe = node->getAttribute(SGPropertyNode::LISTENER_SAFE);
1841 SG_LOG(SG_NASAL, SG_DEV_ALERT,
"ERROR: Cannot add listener to tied property " <<
1846 naRef code = argc > 1 ? args[1] : naNil();
1847 if(!(naIsCode(code) || naIsCCode(code) || naIsFunc(code))) {
1848 naRuntimeError(c,
"setlistener() with invalid function argument");
1852 int init = argc > 2 && naIsNum(args[2]) ? int(args[2].num) : 0;
1853 int type = argc > 3 && naIsNum(args[3]) ? int(args[3].num) : 1;
1857 node->addChangeListener(nl,
init != 0);
1859 d->_listener[d->_listenerId] = nl;
1860 return naNum(d->_listenerId++);
1867 naRef
id = argc > 0 ? args[0] : naNil();
1868 auto it = d->_listener.find(
int(
id.num));
1869 if (!naIsNum(
id) || it == d->_listener.end() || it->second->_dead) {
1870 naRuntimeError(c,
"removelistener() with invalid listener id");
1874 it->second->_dead =
true;
1875 d->_dead_listener.push_back(it->second);
1876 d->_listener.erase(it);
1877 return naNum(d->_listener.size());
1882 if (d->_loadList.empty())
1883 d->_delay_load =
true;
1884 d->_loadList.push(data);
1889 d->_unloadList.push(data);
1894 if (d->_commands.find(
name) != d->_commands.end()) {
1895 SG_LOG(SG_NASAL, SG_WARN,
"duplicate add of command:" <<
name);
1900 d->_commands[
name] = cmd;
1906 auto it = d->_commands.find(
name);
1907 if (it == d->_commands.end()) {
1908 SG_LOG(SG_NASAL, SG_WARN,
"remove of unknwon command:" <<
name);
1913 bool ok =
globals->get_commands()->removeCommand(
name);
1914 d->_commands.erase(it);
1918void FGNasalSys::addPersistentTimer(
TimerObj* pto)
1920 d->_persistentTimers.push_back(pto);
1923void FGNasalSys::removePersistentTimer(
TimerObj* obj)
1925 auto it = std::find(d->_persistentTimers.begin(), d->_persistentTimers.end(), obj);
1926 assert(it != d->_persistentTimers.end());
1927 d->_persistentTimers.erase(it);
1932 SGSubsystemMgr::INIT);
1940 int init,
int type) :
1953 if(_type == 0 && !_init)
1959 _node->removeChangeListener(
this);
1960 _nas->gcRelease(_gcKey);
1963void FGNasalListener::call(SGPropertyNode* which, naRef mode)
1965 if(_active || _dead)
return;
1971 arg[3] = naNum(_node != which);
1972 _nas->
call(_code, 4, arg, naNil());
1978 if(_type < 2 && node != _node)
return;
1979 if(_type > 0 || changed(_node) || _init)
1980 call(node, naNum(0));
1987 if(_type == 2) call(child, naNum(1));
1992 if(_type == 2) call(child, naNum(-1));
1995bool FGNasalListener::changed(SGPropertyNode* node)
1998 props::Type type = node->getType();
1999 if(type == props::NONE)
return false;
2000 if(type == props::UNSPECIFIED)
return true;
2008 long l = node->getLongValue();
2009 result = l != _last_int;
2016 double d = node->getDoubleValue();
2017 result = d != _last_float;
2023 string s = node->getStringValue();
2024 result = s != _last_string;
2034 _c(naSubContext(c)),
2035 _start_element(argc > 1 ? args[1] : naNil()),
2036 _end_element(argc > 2 ? args[2] : naNil()),
2037 _data(argc > 3 ? args[3] : naNil()),
2038 _pi(argc > 4 ? args[4] : naNil())
2044 if(naIsNil(_start_element))
return;
2045 naRef attr = naNewHash(_c);
2046 for(
int i=0;
i<a.size();
i++) {
2047 naRef
name = make_string(a.getName(
i));
2048 naRef value = make_string(a.getValue(
i));
2049 naHash_set(attr,
name, value);
2051 call(_start_element, 2, make_string(tag), attr);
2056 if(!naIsNil(_end_element)) call(_end_element, 1, make_string(tag));
2061 if(!naIsNil(_data)) call(_data, 1, make_string(str, len));
2066 if(!naIsNil(_pi)) call(_pi, 2, make_string(target), make_string(
data));
2069void NasalXMLVisitor::call(naRef
func,
int num, naRef a, naRef b)
2074 naCall(_c,
func, num, args, naNil(), naNil());
2079naRef NasalXMLVisitor::make_string(
const char* s,
int n)
2081 return naStr_fromdata(naNewString(_c),
const_cast<char *
>(s),
2082 n < 0 ? strlen(s) : n);
2090 if (naIsVector(a) && naIsVector(b)) {
2091 const int aSz = naVec_size(a),
2092 bSz = naVec_size(b);
2097 for (
int i = 0;
i < aSz; ++
i) {
2107 if (naIsHash(a) && naIsHash(b)) {
2108 naRef keysVec = naNewVector(ctx);
2109 naHash_keys(keysVec, a);
2110 const auto aSz = naVec_size(keysVec);
2113 if (aSz != naHash_size(b))
2116 for (
int i = 0;
i < aSz;
i++) {
2117 naRef key = naVec_get(keysVec,
i);
2118 naRef aValue, bValue;
2119 if (!naHash_get(a, key, &aValue) || !naHash_get(b, key, &bValue)) {
2133 return naEqual(a, b);
2138 return d->_log.get();
2148 return nasal::Hash(d->_globals, d->_context);
void postinitNasalGUI(naRef globals, naContext c)
void initNasalAircraft(naRef globals, naContext c)
naRef initNasalCanvas(naRef globals, naContext c)
naRef initNasalCondition(naRef globals, naContext c)
static struct @032150236342374176343243244364035346052374146336 funcs[]
naRef initNasalFlightPlan(naRef globals, naContext c)
void shutdownNasalFlightPlan()
naRef initNasalHTTP(naRef globals, naContext c)
void shutdownNasalPositioned()
naRef initNasalPositioned(naRef globals, naContext c)
void postinitNasalPositioned(naRef globals, naContext c)
naRef initNasalPositioned_cppbind(naRef globals, naContext c)
naRef initNasalSGPath(naRef globals, naContext c)
naRef initNasalString(naRef globals, naRef string, naContext c)
static void f_timerObj_setSimTime(TimerObj &timer, naContext c, naRef value)
static naRef f_custom_stat(naContext ctx, naRef me, int argc, naRef *args)
static FGNasalSys * nasalSys
nasal::Ghost< TimeStampObjRef > NasalTimeStampObj
bool pathSortPredicate(const SGPath &p1, const SGPath &p2)
nasal::Ghost< TimerObjRef > NasalTimerObj
static naRef f_settimer(naContext c, naRef me, int argc, naRef *args)
static naRef f_getprop(naContext c, naRef me, int argc, naRef *args)
static naRef f_fgcommand(naContext c, naRef me, int argc, naRef *args)
static naRef f_parsexml(naContext c, naRef me, int argc, naRef *args)
static naRef f_print(naContext c, naRef me, int argc, naRef *args)
SGSubsystemMgr::Registrant< FGNasalSys > registrantFGNasalSys(SGSubsystemMgr::INIT)
SGSharedPtr< TimerObj > TimerObjRef
static naRef f_systime(naContext c, naRef me, int argc, naRef *args)
static naRef f_srand(naContext c, naRef me, int argc, naRef *args)
static naRef f_cmdarg(naContext c, naRef me, int argc, naRef *args)
static SGPropertyNode * findnode(naContext c, naRef *vec, int len, bool create=false)
static naRef f_setlistener(naContext c, naRef me, int argc, naRef *args)
static naRef ftype(naContext ctx, const SGPath &f)
static naRef f_makeTimer(naContext c, naRef me, int argc, naRef *args)
static naRef f_setprop(naContext c, naRef me, int argc, naRef *args)
static naRef f_logprint(naContext c, naRef me, int argc, naRef *args)
static naRef f_addCommand(naContext c, naRef me, int argc, naRef *args)
static naRef f_open(naContext c, naRef me, int argc, naRef *args)
static naRef f_md5(naContext c, naRef me, int argc, naRef *args)
Create md5 hash from given string.
static naRef f_abort(naContext c, naRef me, int argc, naRef *args)
void postinitNasalGUI(naRef globals, naContext c)
static naRef f_removelistener(naContext c, naRef me, int argc, naRef *args)
static naRef f_rand(naContext c, naRef me, int argc, naRef *args)
static naRef f_findDataDir(naContext c, naRef me, int argc, naRef *args)
static naRef f_maketimeStamp(naContext c, naRef me, int argc, naRef *args)
static naRef f_removeCommand(naContext c, naRef me, int argc, naRef *args)
int nasalStructEqual(naContext ctx, naRef a, naRef b)
@breif wrapper for naEqual which recursively checks vec/hash equality Probably not very performant.
static naRef f_resolveDataPath(naContext c, naRef me, int argc, naRef *args)
Given a data path, resolve it in FG_ROOT or an FG_AIRCRFT directory.
static naRef f_directory(naContext c, naRef me, int argc, naRef *args)
SGSharedPtr< TimeStampObj > TimeStampObjRef
bool global_nasalMinimalInit
static naRef f_interpolate(naContext c, naRef me, int argc, naRef *args)
static naRef f_makeSingleShot(naContext c, naRef me, int argc, naRef *args)
naRef initNasalUnitTestCppUnit(naRef globals, naContext c)
naRef initNasalUnitTestInSim(naRef globals, naContext c)
Translations: Nasal interface to FGTranslate and related classes (header file)
void shutdownNasalUnitTestInSim()
SGPath resolve_maybe_aircraft_path(const std::string &branch) const
Same as above, but test for non 'Aircraft/' branch paths, and always resolve them against fg_root.
SGPropertyNode * get_props()
virtual ~FGNasalListener()
FGNasalListener(SGPropertyNode *node, naRef code, FGNasalSys *nasal, int key, int id, int init, int type)
virtual void childRemoved(SGPropertyNode *parent, SGPropertyNode *child)
virtual void valueChanged(SGPropertyNode *node)
virtual void childAdded(SGPropertyNode *parent, SGPropertyNode *child)
Nasal model data container.
virtual void valueChanged(SGPropertyNode *node)
FGNasalModuleListener(SGPropertyNode *node)
simgear::BufferedLogCallback * log() const
retrive the associated log object, for displaying log output somewhere (a UI, presumably)
bool removeCommand(const std::string &name)
naRef removeListener(naContext c, int argc, naRef *args)
void setCmdArg(SGPropertyNode *aNode)
void update(double dt) override
bool createModule(const char *moduleName, const char *fileName, const char *src, int len, const SGPropertyNode *cmdarg=0, int argc=0, naRef *args=0)
void setTimer(naContext c, int argc, naRef *args)
void registerToUnload(FGNasalModelData *data)
naRef call(naRef code, int argc, naRef *args, naRef locals)
naRef callMethod(naRef code, naRef self, int argc, naRef *args, naRef locals)
void deleteModule(const char *moduleName)
void registerToLoad(FGNasalModelData *data)
void hashset(naRef hash, const char *key, naRef val)
Set member of specified hash to given value.
naRef callWithContext(naContext ctx, naRef code, int argc, naRef *args, naRef locals)
bool addCommand(naRef func, const std::string &name)
bool reloadModuleFromFile(const std::string &moduleName)
naRef nasalGlobals() const
naRef callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef *args, naRef locals)
naRef setListener(naContext c, int argc, naRef *args)
string_list getAndClearErrorList()
naRef getModule(const std::string &moduleName) const
nasal::Hash getGlobals() const
virtual bool handleCommand(const char *moduleName, const char *fileName, const char *src, const SGPropertyNode *arg=0, SGPropertyNode *root=0)
void globalsSet(const char *key, naRef val)
Set member of globals hash to given value.
bool parseAndRunWithOutput(const std::string &source, std::string &output, std::string &errors)
naRef propNodeGhost(SGPropertyNode *handle)
naRef wrappedPropsNode(SGPropertyNode *aProps)
create Nasal props.Node for an SGPropertyNode* This is the actual ghost, wrapped in a Nasal sugar cla...
bool parseAndRun(const std::string &source)
bool loadModule(SGPath file, const char *moduleName)
static Ptr getInstance()
Get clipboard platform specific instance.
static void init(FGNasalSys *nasal)
Sets up the clipboard and puts all the extension functions into a new "clipboard" namespace.
bool operator()(const SGPropertyNode *aNode, SGPropertyNode *root) override
NasalCommand(FGNasalSys *sys, naRef f, const std::string &name)
virtual void endElement(const char *tag)
virtual void startElement(const char *tag, const XMLAttributes &a)
virtual void pi(const char *target, const char *data)
virtual void data(const char *str, int len)
NasalXMLVisitor(naContext c, int argc, naRef *args)
Timestamp - used to provide millisecond based timing of operations. See SGTimeStamp / 0....
virtual ~TimeStampObj()=default
TimerObj(Context *c, FGNasalSys *sys, naRef f, naRef self, double interval)
void restart(double newInterval)
void setSingleShot(bool aSingleShot)
const std::string & name() const
void setSimTime(bool value)
bool isSingleShot() const
std::vector< std::string > string_list
void initAddonClassesForNasal(naRef globals, naContext c)
void initNasalTranslations(naRef globals, naContext c)
void sentryReportNasalError(const std::string &, const string_list &)
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
naRef naInit_sqlite(naContext c)
NasalTimer(naRef handler, FGNasalSys *sys)
void valueChanged(SGPropertyNode *node) override