first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
include(FlightGearComponent)
set(SOURCES
nasal-props.cxx
NasalAddons.cxx
NasalAircraft.cxx
NasalPositioned.cxx
NasalPositioned_cppbind.cxx
NasalCanvas.cxx
NasalClipboard.cxx
NasalCondition.cxx
NasalHTTP.cxx
NasalString.cxx
NasalModelData.cxx
NasalSGPath.cxx
NasalFlightPlan.cxx
# we don't add this here becuase we need to exclude it the testSuite
# so it can't go nto fgfsObjects library
# NasalUnitTesting.cxx
)
set(HEADERS
NasalSys.hxx
NasalSys_private.hxx
NasalAddons.hxx
NasalAircraft.hxx
NasalPositioned.hxx
NasalCanvas.hxx
NasalClipboard.hxx
NasalCondition.hxx
NasalHTTP.hxx
NasalString.hxx
NasalModelData.hxx
NasalSGPath.hxx
NasalFlightPlan.hxx
)
if(WIN32)
list(APPEND SOURCES ClipboardWindows.cxx)
elseif(APPLE)
list(APPEND SOURCES ClipboardCocoa.mm)
else()
find_package(X11)
if(X11_FOUND)
list(APPEND SOURCES ClipboardX11.cxx)
else()
list(APPEND SOURCES ClipboardFallback.cxx)
endif()
endif()
# NasalSys.cxx is passed as TEST_SOURCES since we need to compile it
# seperately whrn building the test_suite
flightgear_component(Scripting "${SOURCES}" "${HEADERS}" NasalSys.cxx)

View File

@@ -0,0 +1,93 @@
// Cocoa implementation of clipboard access for Nasal
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "NasalClipboard.hxx"
#include <simgear/debug/logstream.hxx>
#include <AppKit/NSPasteboard.h>
#include <Foundation/NSArray.h>
#include <GUI/CocoaHelpers_private.h>
/**
*/
class ClipboardCocoa: public NasalClipboard
{
public:
/**
* Get clipboard contents as text
*/
virtual std::string getText(Type type)
{
CocoaAutoreleasePool pool;
if( type == CLIPBOARD )
{
NSPasteboard* pboard = [NSPasteboard generalPasteboard];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
NSString* nstext = [pboard stringForType:NSStringPboardType];
#else // > 10.5
NSString* nstext = [pboard stringForType:NSPasteboardTypeString];
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
return stdStringFromCocoa(nstext);
}
return "";
}
/**
* Set clipboard contents as text
*/
virtual bool setText(const std::string& text, Type type)
{
CocoaAutoreleasePool pool;
if( type == CLIPBOARD )
{
NSPasteboard* pboard = [NSPasteboard generalPasteboard];
NSString* nstext = stdStringToCocoa(text);
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
NSString* type = NSStringPboardType;
NSArray* types = [NSArray arrayWithObjects: type, nil];
[pboard declareTypes:types owner:nil];
[pboard setString:nstext forType: NSStringPboardType];
#else // > 10.5
NSString* type = NSPasteboardTypeString;
[pboard clearContents];
[pboard setString:nstext forType:NSPasteboardTypeString];
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
return true;
}
return false;
}
protected:
std::string _selection;
};
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::create()
{
return NasalClipboard::Ptr(new ClipboardCocoa);
}

View File

@@ -0,0 +1,64 @@
// Fallback implementation of clipboard access for Nasal. Copy and edit for
// implementing support of other platforms
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "NasalClipboard.hxx"
#include <simgear/debug/logstream.hxx>
/**
* Provide a basic clipboard whose contents are only available to FlightGear
* itself
*/
class ClipboardFallback:
public NasalClipboard
{
public:
/**
* Get clipboard contents as text
*/
virtual std::string getText(Type type)
{
return type == CLIPBOARD ? _clipboard : _selection;
}
/**
* Set clipboard contents as text
*/
virtual bool setText(const std::string& text, Type type)
{
if( type == CLIPBOARD )
_clipboard = text;
else
_selection = text;
return true;
}
protected:
std::string _clipboard,
_selection;
};
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::create()
{
return NasalClipboard::Ptr(new ClipboardFallback);
}

View File

@@ -0,0 +1,108 @@
// Windows implementation of clipboard access for Nasal
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "NasalClipboard.hxx"
#include <simgear/debug/logstream.hxx>
#include <windows.h>
/**
* Windows does only support on clipboard and no selection. We fake also the X11
* selection buffer - at least inside FlightGear
*/
class ClipboardWindows:
public NasalClipboard
{
public:
/**
* Get clipboard contents as text
*/
virtual std::string getText(Type type)
{
if( type == CLIPBOARD )
{
std::string data;
if( !OpenClipboard(NULL) )
return data;
HANDLE hData = GetClipboardData( CF_TEXT );
char* buff = (char*)GlobalLock( hData );
if (buff)
data = buff;
GlobalUnlock( hData );
CloseClipboard();
return data;
}
else
return _selection;
}
/**
* Set clipboard contents as text
*/
virtual bool setText(const std::string& text, Type type)
{
if( type == CLIPBOARD )
{
if( !OpenClipboard(NULL) )
return false;
bool ret = true;
if( !EmptyClipboard() )
ret = false;
else if( !text.empty() )
{
HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1);
if( !hGlob )
ret = false;
else
{
memcpy(GlobalLock(hGlob), (char*)&text[0], text.size() + 1);
GlobalUnlock(hGlob);
if( !SetClipboardData(CF_TEXT, hGlob) )
{
GlobalFree(hGlob);
ret = false;
}
}
}
CloseClipboard();
return ret;
}
else
{
_selection = text;
return true;
}
}
protected:
std::string _selection;
};
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::create()
{
return NasalClipboard::Ptr(new ClipboardWindows);
}

View File

@@ -0,0 +1,367 @@
// X11 implementation of clipboard access for Nasal
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/*
* See the following links for more information on X11 clipboard:
*
* http://www.jwz.org/doc/x-cut-and-paste.html
* http://michael.toren.net/mirrors/doc/X-copy+paste.txt
* https://github.com/kfish/xsel/blob/master/xsel.c
*/
#include "NasalClipboard.hxx"
#include <simgear/debug/logstream.hxx>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <cassert>
class ClipboardX11:
public NasalClipboard
{
public:
ClipboardX11():
// REVIEW: Memory Leak - 55,397 (104 direct, 55,293 indirect) bytes in 1 blocks are definitely lost
_display( XOpenDisplay(NULL) ),
_window( XCreateSimpleWindow(
_display,
DefaultRootWindow(_display),
0, 0, 1, 1, // dummy dimensions -> window will never be mapped
0,
0, 0
) ),
_atom_targets( XInternAtom(_display, "TARGETS", False) ),
_atom_text( XInternAtom(_display, "TEXT", False) ),
_atom_utf8( XInternAtom(_display, "UTF8_STRING", False) ),
_atom_primary( XInternAtom(_display, "PRIMARY", False) ),
_atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
{
assert(_display);
}
virtual ~ClipboardX11()
{
// Ensure we get rid of any selection ownership
if( XGetSelectionOwner(_display, _atom_primary) )
XSetSelectionOwner(_display, _atom_primary, None, CurrentTime);
if( XGetSelectionOwner(_display, _atom_clipboard) )
XSetSelectionOwner(_display, _atom_clipboard, None, CurrentTime);
}
/**
* We need to run an event queue to check for selection request
*/
virtual void update()
{
while( XPending(_display) )
{
XEvent event;
XNextEvent(_display, &event);
handleEvent(event);
}
}
/**
* Get clipboard contents as text
*/
virtual std::string getText(Type type)
{
Atom atom_type = typeToAtom(type);
//Request a list of possible conversions
XConvertSelection( _display, atom_type, _atom_targets, atom_type,
_window, CurrentTime );
Atom requested_type = None;
bool sent_request = false;
for(int cnt = 0; cnt < 5;)
{
XEvent event;
XNextEvent(_display, &event);
if( event.type != SelectionNotify )
{
handleEvent(event);
continue;
}
Atom target = event.xselection.target;
if(event.xselection.property == None)
{
if( target == _atom_targets )
// If TARGETS can not be converted no selection is available
break;
SG_LOG
(
SG_NASAL,
SG_WARN,
"ClipboardX11::getText: Conversion failed: "
"target=" << getAtomName(target)
);
break;
}
//If we're being given a list of targets (possible conversions)
if(target == _atom_targets && !sent_request)
{
sent_request = true;
requested_type = XA_STRING; // TODO select other type
XConvertSelection( _display, atom_type, requested_type, atom_type,
_window, CurrentTime );
}
else if(target == requested_type)
{
Property prop = readProperty(_window, atom_type);
if( prop.format != 8 )
{
SG_LOG
(
SG_NASAL,
SG_WARN,
"ClipboardX11::getText: can only handle 8-bit data (is "
<< prop.format << "-bit) -> retry "
<< cnt++
);
XFree(prop.data);
continue;
}
std::string result((const char*)prop.data, prop.num_items);
XFree(prop.data);
return result;
}
else
{
SG_LOG
(
SG_NASAL,
SG_WARN,
"ClipboardX11::getText: wrong target: " << getAtomName(target)
);
break;
}
}
return std::string();
}
/**
* Set clipboard contents as text
*/
virtual bool setText(const std::string& text, Type type)
{
Atom atom_type = typeToAtom(type);
XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
if( XGetSelectionOwner(_display, atom_type) != _window )
{
SG_LOG
(
SG_NASAL,
SG_ALERT,
"ClipboardX11::setText: failed to get selection owner!"
);
return false;
}
// We need to store the text for sending it to another application upon
// request
if( type == CLIPBOARD )
_clipboard = text;
else
_selection = text;
return true;
}
protected:
Display *_display;
Window _window;
Atom _atom_targets,
_atom_text,
_atom_utf8,
_atom_primary,
_atom_clipboard;
std::string _clipboard,
_selection;
void handleEvent(const XEvent& event)
{
switch( event.type )
{
case SelectionRequest:
handleSelectionRequest(event.xselectionrequest);
break;
case SelectionClear:
if( event.xselectionclear.selection == _atom_clipboard )
_clipboard.clear();
else
_selection.clear();
break;
default:
SG_LOG
(
SG_NASAL,
SG_WARN,
"ClipboardX11: unexpected XEvent: " << event.type
);
break;
}
}
void handleSelectionRequest(const XSelectionRequestEvent& sel_req)
{
SG_LOG
(
SG_NASAL,
SG_DEBUG,
"ClipboardX11: handle selection request: "
"selection=" << getAtomName(sel_req.selection) << ", "
"target=" << getAtomName(sel_req.target)
);
const std::string& buf = sel_req.selection == _atom_clipboard
? _clipboard
: _selection;
// Prepare response to notify whether we have written to the property or
// are unable to do the conversion
XSelectionEvent response;
response.type = SelectionNotify;
response.display = sel_req.display;
response.requestor = sel_req.requestor;
response.selection = sel_req.selection;
response.target = sel_req.target;
response.property = sel_req.property;
response.time = sel_req.time;
if( sel_req.target == _atom_targets )
{
static Atom supported[] = {
XA_STRING,
_atom_text,
_atom_utf8
};
changeProperty
(
sel_req.requestor,
sel_req.property,
sel_req.target,
&supported,
sizeof(supported)
);
}
else if( sel_req.target == XA_STRING
|| sel_req.target == _atom_text
|| sel_req.target == _atom_utf8 )
{
changeProperty
(
sel_req.requestor,
sel_req.property,
sel_req.target,
buf.data(),
buf.size()
);
}
else
{
// Notify requestor that we are unable to perform requested conversion
response.property = None;
}
XSendEvent(_display, sel_req.requestor, False, 0, (XEvent*)&response);
}
void changeProperty( Window w,
Atom prop,
Atom type,
const void *data,
size_t size )
{
XChangeProperty
(
_display, w, prop, type, 8, PropModeReplace,
static_cast<const unsigned char*>(data), size
);
}
struct Property
{
unsigned char *data;
int format;
unsigned long num_items;
Atom type;
};
// Get all data from a property
Property readProperty(Window w, Atom property)
{
Atom actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *ret = 0;
int read_bytes = 1024;
//Keep trying to read the property until there are no
//bytes unread.
do
{
if( ret )
XFree(ret);
XGetWindowProperty
(
_display, w, property, 0, read_bytes, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after,
&ret
);
read_bytes *= 2;
} while( bytes_after );
Property p = {ret, actual_format, nitems, actual_type};
return p;
}
std::string getAtomName(Atom atom) const
{
return atom == None ? "None" : XGetAtomName(_display, atom);
}
Atom typeToAtom(Type type) const
{
return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
}
};
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::create()
{
return NasalClipboard::Ptr(new ClipboardX11);
}

View File

@@ -0,0 +1,212 @@
// -*- coding: utf-8 -*-
//
// NasalAddons.cxx --- Expose add-on classes to Nasal
// Copyright (C) 2017, 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <memory>
#include <string>
#include <cassert>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/nasal.h>
#include <simgear/structure/exception.hxx>
#include <Add-ons/addon_fwd.hxx>
#include <Add-ons/Addon.hxx>
#include <Add-ons/AddonManager.hxx>
#include <Add-ons/AddonVersion.hxx>
#include <Add-ons/contacts.hxx>
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * AddonManager *
// ***************************************************************************
static const std::unique_ptr<AddonManager>& getAddonMgrWithCheck()
{
const auto& addonMgr = AddonManager::instance();
if (!addonMgr) {
throw sg_exception("trying to access the AddonManager from Nasal, "
"however it is not initialized");
}
return addonMgr;
}
static naRef f_registeredAddons(const nasal::CallContext& ctx)
{
const auto& addonMgr = getAddonMgrWithCheck();
return ctx.to_nasal(addonMgr->registeredAddons());
}
static naRef f_isAddonRegistered(const nasal::CallContext& ctx)
{
const auto& addonMgr = getAddonMgrWithCheck();
std::string addonId = ctx.requireArg<std::string>(0);
return ctx.to_nasal(addonMgr->isAddonRegistered(addonId));
}
static naRef f_loadedAddons(const nasal::CallContext& ctx)
{
const auto& addonMgr = getAddonMgrWithCheck();
return ctx.to_nasal(addonMgr->loadedAddons());
}
static naRef f_isAddonLoaded(const nasal::CallContext& ctx)
{
const auto& addonMgr = getAddonMgrWithCheck();
std::string addonId = ctx.requireArg<std::string>(0);
return ctx.to_nasal(addonMgr->isAddonLoaded(addonId));
}
static naRef f_getAddon(const nasal::CallContext& ctx)
{
const auto& addonMgr = getAddonMgrWithCheck();
return ctx.to_nasal(addonMgr->getAddon(ctx.requireArg<std::string>(0)));
}
static void wrapAddonManagerMethods(nasal::Hash& addonsModule)
{
addonsModule.set("registeredAddons", &f_registeredAddons);
addonsModule.set("isAddonRegistered", &f_isAddonRegistered);
addonsModule.set("loadedAddons", &f_loadedAddons);
addonsModule.set("isAddonLoaded", &f_isAddonLoaded);
addonsModule.set("getAddon", &f_getAddon);
}
// ***************************************************************************
// * AddonVersion *
// ***************************************************************************
// Create a new AddonVersion instance.
//
// addons.AddonVersion.new(versionString)
// addons.AddonVersion.new(major[, minor[, patchLevel[, suffix]]])
//
// where:
// - 'major', 'minor' and 'patchLevel' are integers;
// - 'suffix' is a string such as "", "a3", "b12" or "rc4" (resp. meaning:
// release, alpha 3, beta 12, release candidate 4) Suffixes for
// development releases are also allowed, such as ".dev4" (sorts before
// the release as well as any alphas, betas and rcs for the release) and
// "a3.dev10" (sorts before "a3.dev11", "a3.dev12", "a3", etc.).
//
// For details, see <https://www.python.org/dev/peps/pep-0440/> which is a
// proper superset of what is allowed here.
naRef f_createAddonVersion(const nasal::CallContext& ctx)
{
int major = 0, minor = 0, patchLevel = 0;
std::string suffix;
if (ctx.argc == 0 || ctx.argc > 4) {
ctx.runtimeError(
"AddonVersion.new(versionString) or "
"AddonVersion.new(major[, minor[, patchLevel[, suffix]]])"
);
}
if (ctx.argc == 1) {
naRef arg1 = ctx.args[0];
if (naIsString(arg1)) {
return ctx.to_nasal(AddonVersionRef(new AddonVersion(naStr_data(arg1))));
} else if (naIsNum(arg1)) {
AddonVersionRef ref{new AddonVersion(arg1.num, minor, patchLevel,
AddonVersionSuffix(suffix))};
return ctx.to_nasal(std::move(ref));
} else {
ctx.runtimeError(
"AddonVersion.new(versionString) or "
"AddonVersion.new(major[, minor[, patchLevel[, suffix]]])"
);
}
}
assert(ctx.argc > 0);
if (!ctx.isNumeric(0)) {
ctx.runtimeError(
"addons.AddonVersion.new() requires major number as an integer"
);
}
major = ctx.requireArg<int>(0);
if (ctx.argc > 1) {
if (!ctx.isNumeric(1)) {
ctx.runtimeError(
"addons.AddonVersion.new() requires minor number as an integer"
);
}
minor = ctx.requireArg<int>(1);
}
if (ctx.argc > 2) {
if (!ctx.isNumeric(2)) {
ctx.runtimeError(
"addons.AddonVersion.new() requires patch level as an integer"
);
}
patchLevel = ctx.requireArg<int>(2);
}
if (ctx.argc > 3) {
if (!ctx.isString(3)) {
ctx.runtimeError(
"addons.AddonVersion.new() requires suffix as a string"
);
}
suffix = ctx.requireArg<std::string>(3);
}
assert(ctx.argc <= 4);
return ctx.to_nasal(
AddonVersionRef(new AddonVersion(major, minor, patchLevel,
AddonVersionSuffix(suffix))));
}
void initAddonClassesForNasal(naRef globals, naContext c)
{
nasal::Hash globalsModule(globals, c);
nasal::Hash addonsModule = globalsModule.createHash("addons");
wrapAddonManagerMethods(addonsModule);
Addon::setupGhost(addonsModule);
Contact::setupGhost(addonsModule);
Author::setupGhost(addonsModule);
Maintainer::setupGhost(addonsModule);
AddonVersion::setupGhost(addonsModule);
addonsModule.createHash("AddonVersion").set("new", &f_createAddonVersion);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,37 @@
// -*- coding: utf-8 -*-
//
// NasalAddons.hxx --- Expose add-on classes to Nasal
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_NASAL_INTERFACE_HXX
#define FG_ADDON_NASAL_INTERFACE_HXX
#include <simgear/nasal/nasal.h>
namespace flightgear
{
namespace addons
{
void initAddonClassesForNasal(naRef globals, naContext c);
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_NASAL_INTERFACE_HXX

View File

@@ -0,0 +1,49 @@
// Expose aircraft related data to Nasal
//
// Copyright (C) 2014 Thomas Geymayer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalAircraft.hxx"
#include <Aircraft/FlightHistory.hxx>
#include <Main/globals.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
//------------------------------------------------------------------------------
static naRef f_getHistory(const nasal::CallContext& ctx)
{
FGFlightHistory* history =
static_cast<FGFlightHistory*>(globals->get_subsystem("history"));
if( !history )
ctx.runtimeError("Failed to get 'history' subsystem");
return ctx.to_nasal(history);
}
//------------------------------------------------------------------------------
void initNasalAircraft(naRef globals, naContext c)
{
nasal::Ghost<SGSharedPtr<FGFlightHistory> >::init("FGFlightHistory")
.method("pathForHistory", &FGFlightHistory::pathForHistory);
nasal::Hash aircraft_module = nasal::Hash(globals, c).createHash("aircraft");
aircraft_module.set("history", &f_getHistory);
}

View File

@@ -0,0 +1,27 @@
//@file Expose aircraft related data to Nasal
//
// Copyright (C) 2014 Thomas Geymayer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_AIRCRAFT_HXX
#define SCRIPTING_NASAL_AIRCRAFT_HXX
#include <simgear/nasal/nasal.h>
void initNasalAircraft(naRef globals, naContext c);
#endif // SCRIPTING_NASAL_AIRCRAFT_HXX

View File

@@ -0,0 +1,658 @@
// NasalCanvas.cxx -- expose Canvas classes to Nasal
//
// Written by James Turner, started 2012.
//
// Copyright (C) 2012 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "NasalCanvas.hxx"
#include <Canvas/canvas_mgr.hxx>
#include <Canvas/gui_mgr.hxx>
#include <Main/globals.hxx>
#include <Scripting/NasalSys.hxx>
#include <osgGA/GUIEventAdapter>
#include <simgear/sg_inlines.h>
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasWindow.hxx>
#include <simgear/canvas/elements/CanvasElement.hxx>
#include <simgear/canvas/elements/CanvasText.hxx>
#include <simgear/canvas/events/CustomEvent.hxx>
#include <simgear/canvas/events/KeyboardEvent.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/canvas/layout/BoxLayout.hxx>
#include <simgear/canvas/layout/GridLayout.hxx>
#include <simgear/canvas/layout/NasalWidget.hxx>
#include <simgear/canvas/layout/SpacerItem.hxx>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
extern naRef propNodeGhostCreate(naContext c, SGPropertyNode* n);
namespace sc = simgear::canvas;
template<class Element>
naRef elementGetNode(Element& element, naContext c)
{
return propNodeGhostCreate(c, element.getProps());
}
typedef nasal::Ghost<sc::EventPtr> NasalEvent;
typedef nasal::Ghost<sc::CustomEventPtr> NasalCustomEvent;
typedef nasal::Ghost<sc::DeviceEventPtr> NasalDeviceEvent;
typedef nasal::Ghost<sc::KeyboardEventPtr> NasalKeyboardEvent;
typedef nasal::Ghost<sc::MouseEventPtr> NasalMouseEvent;
struct CustomEventDetailWrapper;
typedef SGSharedPtr<CustomEventDetailWrapper> CustomEventDetailPtr;
typedef nasal::Ghost<CustomEventDetailPtr> NasalCustomEventDetail;
typedef nasal::Ghost<simgear::PropertyBasedElementPtr> NasalPropertyBasedElement;
typedef nasal::Ghost<sc::CanvasPtr> NasalCanvas;
typedef nasal::Ghost<sc::ElementPtr> NasalElement;
typedef nasal::Ghost<sc::GroupPtr> NasalGroup;
typedef nasal::Ghost<sc::TextPtr> NasalText;
typedef nasal::Ghost<sc::ImagePtr> NasalImage;
typedef nasal::Ghost<sc::LayoutItemRef> NasalLayoutItem;
typedef nasal::Ghost<sc::LayoutRef> NasalLayout;
typedef nasal::Ghost<sc::BoxLayoutRef> NasalBoxLayout;
typedef nasal::Ghost<sc::GridLayoutRef> NasalGridLayout;
using NasalSpacerItem = nasal::Ghost<sc::SpacerItemRef>;
typedef nasal::Ghost<sc::WindowPtr> NasalWindow;
naRef to_nasal_helper(naContext c, const osg::BoundingBox& bb)
{
std::vector<float> bb_vec(4);
bb_vec[0] = bb._min.x();
bb_vec[1] = bb._min.y();
bb_vec[2] = bb._max.x();
bb_vec[3] = bb._max.y();
return nasal::to_nasal(c, bb_vec);
}
SGPropertyNode* from_nasal_helper(naContext c, naRef ref, SGPropertyNode**)
{
SGPropertyNode* props = ghostToPropNode(ref);
if( !props )
naRuntimeError(c, "Not a SGPropertyNode ghost.");
return props;
}
CanvasMgr& requireCanvasMgr(const nasal::ContextWrapper& ctx)
{
CanvasMgr* canvas_mgr =
static_cast<CanvasMgr*>(globals->get_subsystem("Canvas"));
if( !canvas_mgr )
ctx.runtimeError("Failed to get Canvas subsystem");
return *canvas_mgr;
}
GUIMgr& requireGUIMgr(const nasal::ContextWrapper& ctx)
{
GUIMgr* mgr =
static_cast<GUIMgr*>(globals->get_subsystem("CanvasGUI"));
if( !mgr )
ctx.runtimeError("Failed to get CanvasGUI subsystem");
return *mgr;
}
/**
* Create new Canvas and get ghost for it.
*/
static naRef f_createCanvas(const nasal::CallContext& ctx)
{
return ctx.to_nasal(requireCanvasMgr(ctx).createCanvas());
}
/**
* Create new Window and get ghost for it.
*/
static naRef f_createWindow(const nasal::CallContext& ctx)
{
return ctx.to_nasal<sc::WindowWeakPtr>
(
requireGUIMgr(ctx).createWindow( ctx.getArg<std::string>(0) )
);
}
/**
* Get ghost for existing Canvas.
*/
static naRef f_getCanvas(const nasal::CallContext& ctx)
{
SGPropertyNode& props = *ctx.requireArg<SGPropertyNode*>(0);
CanvasMgr& canvas_mgr = requireCanvasMgr(ctx);
sc::CanvasPtr canvas;
if( canvas_mgr.getPropertyRoot() == props.getParent() )
{
// get a canvas specified by its root node
canvas = canvas_mgr.getCanvas( props.getIndex() );
if( !canvas || canvas->getProps() != &props )
return naNil();
}
else
{
// get a canvas by name
if( props.hasValue("name") )
canvas = canvas_mgr.getCanvas( props.getStringValue("name") );
else if( props.hasValue("index") )
canvas = canvas_mgr.getCanvas( props.getIntValue("index") );
}
return ctx.to_nasal(canvas);
}
naRef f_canvasCreateGroup(sc::Canvas& canvas, const nasal::CallContext& ctx)
{
return ctx.to_nasal( canvas.createGroup(ctx.getArg<std::string>(0)) );
}
/**
* Get group containing all gui windows
*/
naRef f_getDesktop(const nasal::CallContext& ctx)
{
return ctx.to_nasal(requireGUIMgr(ctx).getDesktop());
}
naRef f_setInputFocus(const nasal::CallContext& ctx)
{
requireGUIMgr(ctx).setInputFocus(ctx.requireArg<sc::WindowPtr>(0));
return naNil();
}
naRef f_grabPointer(const nasal::CallContext& ctx)
{
return ctx.to_nasal(
requireGUIMgr(ctx).grabPointer(ctx.requireArg<sc::WindowPtr>(0))
);
}
naRef f_ungrabPointer(const nasal::CallContext& ctx)
{
requireGUIMgr(ctx).ungrabPointer(ctx.requireArg<sc::WindowPtr>(0));
return naNil();
}
static naRef f_groupCreateChild(sc::Group& group, const nasal::CallContext& ctx)
{
return ctx.to_nasal( group.createChild( ctx.requireArg<std::string>(0),
ctx.getArg<std::string>(1) ) );
}
static sc::ElementPtr f_groupGetChild(sc::Group& group, SGPropertyNode* node)
{
return group.getChild(node);
}
static void propElementSetData( simgear::PropertyBasedElement& el,
const std::string& name,
const nasal::ContextWrapper& ctx,
naRef ref )
{
if( naIsNil(ref) )
return el.removeDataProp(name);
std::string val = ctx.from_nasal<std::string>(ref);
char* end = NULL;
long val_long = strtol(val.c_str(), &end, 10);
if( !*end )
return el.setDataProp(name, val_long);
double val_double = strtod(val.c_str(), &end);
if( !*end )
return el.setDataProp(name, val_double);
el.setDataProp(name, val);
}
/**
* Accessor for HTML5 data properties.
*
* # set single property:
* el.data("myKey", 5);
*
* # set multiple properties
* el.data({myProp1: 12, myProp2: "test"});
*
* # get value of properties
* el.data("myKey"); # 5
* el.data("myProp2"); # "test"
*
* # remove a single property
* el.data("myKey", nil);
*
* # remove multiple properties
* el.data({myProp1: nil, myProp2: nil});
*
* # set and remove multiple properties
* el.data({newProp: "some text...", removeProp: nil});
*
*
* @see http://api.jquery.com/data/
*/
static naRef f_propElementData( simgear::PropertyBasedElement& el,
const nasal::CallContext& ctx )
{
if( ctx.isHash(0) )
{
// Add/delete properties given as hash
nasal::Hash obj = ctx.requireArg<nasal::Hash>(0);
for(nasal::Hash::iterator it = obj.begin(); it != obj.end(); ++it)
propElementSetData(el, it->getKey(), ctx, it->getValue<naRef>());
return ctx.to_nasal(&el);
}
std::string name = ctx.getArg<std::string>(0);
if( !name.empty() )
{
if( ctx.argc == 1 )
{
// name + additional argument -> add/delete property
SGPropertyNode* node = el.getDataProp<SGPropertyNode*>(name);
if( !node )
return naNil();
return ctx.to_nasal( node->getStringValue() );
}
else
{
// only name -> get property
propElementSetData(el, name, ctx, ctx.requireArg<naRef>(1));
return ctx.to_nasal(&el);
}
}
return naNil();
}
static naRef f_createCustomEvent(const nasal::CallContext& ctx)
{
std::string const& type = ctx.requireArg<std::string>(0);
if( type.empty() )
return naNil();
bool bubbles = false;
simgear::StringMap detail;
if( ctx.isHash(1) )
{
nasal::Hash const& cfg = ctx.requireArg<nasal::Hash>(1);
naRef na_detail = cfg.get("detail");
if( naIsHash(na_detail) )
detail = ctx.from_nasal<simgear::StringMap>(na_detail);
bubbles = cfg.get<bool>("bubbles");
}
return ctx.to_nasal(
sc::CustomEventPtr(new sc::CustomEvent(type, bubbles, detail))
);
}
struct CustomEventDetailWrapper:
public SGReferenced
{
sc::CustomEventPtr _event;
CustomEventDetailWrapper(const sc::CustomEventPtr& event):
_event(event)
{
}
bool _get( const std::string& key,
std::string& value_out ) const
{
if( !_event )
return false;
simgear::StringMap::const_iterator it = _event->detail.find(key);
if( it == _event->detail.end() )
return false;
value_out = it->second;
return true;
}
bool _set( const std::string& key,
const std::string& value )
{
if( !_event )
return false;
_event->detail[ key ] = value;
return true;
}
};
static naRef f_customEventGetDetail( sc::CustomEvent& event,
naContext c )
{
return nasal::to_nasal(
c,
CustomEventDetailPtr(new CustomEventDetailWrapper(&event))
);
}
static naRef f_boxLayoutAddItem( sc::BoxLayout& box,
const nasal::CallContext& ctx )
{
box.addItem( ctx.requireArg<sc::LayoutItemRef>(0),
ctx.getArg<int>(1),
ctx.getArg<int>(2, sc::AlignFill) );
return naNil();
}
static naRef f_boxLayoutInsertItem( sc::BoxLayout& box,
const nasal::CallContext& ctx )
{
box.insertItem( ctx.requireArg<int>(0),
ctx.requireArg<sc::LayoutItemRef>(1),
ctx.getArg<int>(2),
ctx.getArg<int>(3, sc::AlignFill) );
return naNil();
}
static naRef f_boxLayoutAddStretch( sc::BoxLayout& box,
const nasal::CallContext& ctx )
{
box.addStretch( ctx.getArg<int>(0) );
return naNil();
}
static naRef f_boxLayoutInsertStretch( sc::BoxLayout& box,
const nasal::CallContext& ctx )
{
box.insertStretch( ctx.requireArg<int>(0),
ctx.getArg<int>(1) );
return naNil();
}
template<class Type, class Base>
static naRef f_newAsBase(const nasal::CallContext& ctx)
{
return ctx.to_nasal<Base*>(new Type());
}
static naRef f_imageFillRect(sc::Image& img, const nasal::CallContext& ctx)
{
const SGRecti r = ctx.requireArg<SGRecti>(0);
if (ctx.isString(1)) {
img.fillRect(r, ctx.getArg<std::string>(1));
} else {
img.fillRect(r, ctx.requireArg<osg::Vec4>(1));
}
return naNil();
}
static naRef f_imageSetPixel(sc::Image& img, const nasal::CallContext& ctx)
{
const int s = ctx.requireArg<int>(0);
const int t = ctx.requireArg<int>(1);
if (ctx.isString(2)) {
img.setPixel(s, t, ctx.getArg<std::string>(2));
} else {
img.setPixel(s, t, ctx.requireArg<osg::Vec4>(2));
}
return naNil();
}
static naRef f_gridLayoutAddItem(sc::GridLayout& grid,
const nasal::CallContext& ctx)
{
grid.addItem(ctx.requireArg<sc::LayoutItemRef>(0),
ctx.requireArg<int>(1),
ctx.requireArg<int>(2),
ctx.getArg<int>(3, 1),
ctx.getArg<int>(4, 1));
return naNil();
}
static naRef f_newGridLayout(const nasal::CallContext& ctx)
{
return ctx.to_nasal(new sc::GridLayout);
}
static naRef f_newSpacerItem(const nasal::CallContext& ctx)
{
return ctx.to_nasal(new sc::SpacerItem);
}
naRef initNasalCanvas(naRef globals, naContext c)
{
nasal::Hash globals_module(globals, c),
canvas_module = globals_module.createHash("canvas");
nasal::Object::setupGhost();
//----------------------------------------------------------------------------
// Events
using osgGA::GUIEventAdapter;
NasalEvent::init("canvas.Event")
.member("type", &sc::Event::getTypeString)
.member("target", &sc::Event::getTarget)
.member("currentTarget", &sc::Event::getCurrentTarget)
.member("defaultPrevented", &sc::Event::defaultPrevented)
.method("stopPropagation", &sc::Event::stopPropagation)
.method("preventDefault", &sc::Event::preventDefault);
NasalCustomEvent::init("canvas.CustomEvent")
.bases<NasalEvent>()
.member("detail", &f_customEventGetDetail, &sc::CustomEvent::setDetail);
NasalCustomEventDetail::init("canvas.CustomEventDetail")
._get(&CustomEventDetailWrapper::_get)
._set(&CustomEventDetailWrapper::_set);
canvas_module.createHash("CustomEvent")
.set("new", &f_createCustomEvent);
NasalDeviceEvent::init("canvas.DeviceEvent")
.bases<NasalEvent>()
.member("modifiers", &sc::DeviceEvent::getModifiers)
.member("ctrlKey", &sc::DeviceEvent::ctrlKey)
.member("shiftKey", &sc::DeviceEvent::shiftKey)
.member("altKey", &sc::DeviceEvent::altKey)
.member("metaKey", &sc::DeviceEvent::metaKey);
NasalKeyboardEvent::init("canvas.KeyboardEvent")
.bases<NasalDeviceEvent>()
.member("key", &sc::KeyboardEvent::key)
.member("location", &sc::KeyboardEvent::location)
.member("repeat", &sc::KeyboardEvent::repeat)
.member("charCode", &sc::KeyboardEvent::charCode)
.member("keyCode", &sc::KeyboardEvent::keyCode);
NasalMouseEvent::init("canvas.MouseEvent")
.bases<NasalDeviceEvent>()
.member("screenX", &sc::MouseEvent::getScreenX)
.member("screenY", &sc::MouseEvent::getScreenY)
.member("clientX", &sc::MouseEvent::getClientX)
.member("clientY", &sc::MouseEvent::getClientY)
.member("localX", &sc::MouseEvent::getLocalX)
.member("localY", &sc::MouseEvent::getLocalY)
.member("deltaX", &sc::MouseEvent::getDeltaX)
.member("deltaY", &sc::MouseEvent::getDeltaY)
.member("button", &sc::MouseEvent::getButton)
.member("buttons", &sc::MouseEvent::getButtonMask)
.member("click_count", &sc::MouseEvent::getCurrentClickCount);
//----------------------------------------------------------------------------
// Canvas & elements
NasalPropertyBasedElement::init("PropertyBasedElement")
.method("data", &f_propElementData);
NasalCanvas::init("Canvas")
.bases<NasalPropertyBasedElement>()
.bases<nasal::ObjectRef>()
.member("_node_ghost", &elementGetNode<sc::Canvas>)
.member("size_x", &sc::Canvas::getSizeX)
.member("size_y", &sc::Canvas::getSizeY)
.method("_createGroup", &f_canvasCreateGroup)
.method("_getGroup", &sc::Canvas::getGroup)
.method( "addEventListener",
static_cast<bool (sc::Canvas::*)( const std::string&,
const sc::EventListener& )>
(&sc::Canvas::addEventListener) )
.method("dispatchEvent", &sc::Canvas::dispatchEvent)
.method("setLayout", &sc::Canvas::setLayout)
.method("setFocusElement", &sc::Canvas::setFocusElement)
.method("clearFocusElement", &sc::Canvas::clearFocusElement);
canvas_module.set("_newCanvasGhost", f_createCanvas);
canvas_module.set("_getCanvasGhost", f_getCanvas);
NasalElement::init("canvas.Element")
.bases<NasalPropertyBasedElement>()
.member("_node_ghost", &elementGetNode<sc::Element>)
.method("_getParent", &sc::Element::getParent)
.method("_getCanvas", &sc::Element::getCanvas)
.method("addEventListener", &sc::Element::addEventListener)
.method("setFocus", &sc::Element::setFocus)
.method("dispatchEvent", &sc::Element::dispatchEvent)
.method("getBoundingBox", &sc::Element::getBoundingBox)
.method("getTightBoundingBox", &sc::Element::getTightBoundingBox);
NasalGroup::init("canvas.Group")
.bases<NasalElement>()
.method("_createChild", &f_groupCreateChild)
.method( "_getChild", &f_groupGetChild)
.method("_getElementById", &sc::Group::getElementById);
NasalText::init("canvas.Text")
.bases<NasalElement>()
.method("heightForWidth", &sc::Text::heightForWidth)
.method("maxWidth", &sc::Text::maxWidth)
.method("lineCount", &sc::Text::lineCount)
.method("lineLength", &sc::Text::lineLength)
.method("getNearestCursor", &sc::Text::getNearestCursor)
.method("getCursorPos", &sc::Text::getCursorPos);
NasalImage::init("canvas.Image")
.bases<NasalElement>()
.method("fillRect", &f_imageFillRect)
.method("setPixel", &f_imageSetPixel)
.method("dirtyPixels", &sc::Image::dirtyPixels);
//----------------------------------------------------------------------------
// Layouting
#define ALIGN_ENUM_MAPPING(key, val, comment) canvas_module.set(#key, sc::key);
# include <simgear/canvas/layout/AlignFlag_values.hxx>
#undef ALIGN_ENUM_MAPPING
void (sc::LayoutItem::*f_layoutItemSetContentsMargins)(int, int, int, int)
= &sc::LayoutItem::setContentsMargins;
NasalLayoutItem::init("canvas.LayoutItem")
.method("getCanvas", &sc::LayoutItem::getCanvas)
.method("setCanvas", &sc::LayoutItem::setCanvas)
.method("getParent", &sc::LayoutItem::getParent)
.method("setParent", &sc::LayoutItem::setParent)
.method("setContentsMargins", f_layoutItemSetContentsMargins)
.method("setContentsMargin", &sc::LayoutItem::setContentsMargin)
.method("sizeHint", &sc::LayoutItem::sizeHint)
.method("minimumSize", &sc::LayoutItem::minimumSize)
.method("maximumSize", &sc::LayoutItem::maximumSize)
.method("hasHeightForWidth", &sc::LayoutItem::hasHeightForWidth)
.method("heightForWidth", &sc::LayoutItem::heightForWidth)
.method("minimumHeightForWidth", &sc::LayoutItem::minimumHeightForWidth)
.method("setAlignment", &sc::LayoutItem::setAlignment)
.method("alignment", &sc::LayoutItem::alignment)
.method("setVisible", &sc::LayoutItem::setVisible)
.method("isVisible", &sc::LayoutItem::isVisible)
.method("isExplicitlyHidden", &sc::LayoutItem::isExplicitlyHidden)
.method("show", &sc::LayoutItem::show)
.method("hide", &sc::LayoutItem::hide)
.method("setGeometry", &sc::LayoutItem::setGeometry)
.method("geometry", &sc::LayoutItem::geometry)
.method("setGridLocation", &sc::LayoutItem::setGridLocation)
.method("setGridSpan", &sc::LayoutItem::setGridSpan);
sc::NasalWidget::setupGhost(canvas_module);
NasalLayout::init("canvas.Layout")
.bases<NasalLayoutItem>()
.method("addItem", &sc::Layout::addItem)
.method("setSpacing", &sc::Layout::setSpacing)
.method("spacing", &sc::Layout::spacing)
.method("count", &sc::Layout::count)
.method("itemAt", &sc::Layout::itemAt)
.method("takeAt", &sc::Layout::takeAt)
.method("removeItem", &sc::Layout::removeItem)
.method("clear", &sc::Layout::clear);
NasalBoxLayout::init("canvas.BoxLayout")
.bases<NasalLayout>()
.method("addItem", &f_boxLayoutAddItem)
.method("addSpacing", &sc::BoxLayout::addSpacing)
.method("addStretch", &f_boxLayoutAddStretch)
.method("insertItem", &f_boxLayoutInsertItem)
.method("insertSpacing", &sc::BoxLayout::insertSpacing)
.method("insertStretch", &f_boxLayoutInsertStretch)
.method("setStretch", &sc::BoxLayout::setStretch)
.method("setStretchFactor", &sc::BoxLayout::setStretchFactor)
.method("stretch", &sc::BoxLayout::stretch);
NasalGridLayout::init("canvas.GridLayout")
.bases<NasalLayout>()
.method("addItem", &f_gridLayoutAddItem)
.method("setRowStretch", &sc::GridLayout::setRowStretch)
.method("setColumnStretch", &sc::GridLayout::setColumnStretch);
NasalSpacerItem::init("canvas.SpacerItem")
.bases<NasalLayoutItem>();
canvas_module.createHash("HBoxLayout")
.set("new", &f_newAsBase<sc::HBoxLayout, sc::BoxLayout>);
canvas_module.createHash("VBoxLayout")
.set("new", &f_newAsBase<sc::VBoxLayout, sc::BoxLayout>);
canvas_module.createHash("GridLayout")
.set("new", &f_newGridLayout);
canvas_module.createHash("Spacer")
.set("new", &f_newSpacerItem);
//----------------------------------------------------------------------------
// Window
NasalWindow::init("canvas.Window")
.bases<NasalElement>()
.bases<NasalLayoutItem>()
.member("_node_ghost", &elementGetNode<sc::Window>)
.method("_getCanvasDecoration", &sc::Window::getCanvasDecoration)
.method("setLayout", &sc::Window::setLayout);
canvas_module.set("_newWindowGhost", f_createWindow);
canvas_module.set("_getDesktopGhost", f_getDesktop);
canvas_module.set("setInputFocus", f_setInputFocus);
canvas_module.set("grabPointer", f_grabPointer);
canvas_module.set("ungrabPointer", f_ungrabPointer);
return naNil();
}

View File

@@ -0,0 +1,29 @@
// NasalCanvas.hxx -- expose Canvas classes to Nasal
//
// Written by James Turner, started 2012.
//
// Copyright (C) 2012 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_CANVAS_HXX
#define SCRIPTING_NASAL_CANVAS_HXX
#include <simgear/nasal/nasal.h>
naRef initNasalCanvas(naRef globals, naContext c);
#endif // of SCRIPTING_NASAL_CANVAS_HXX

View File

@@ -0,0 +1,106 @@
// X11 implementation of clipboard access for Nasal
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalClipboard.hxx"
#include "NasalSys.hxx"
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <cstddef>
/*
* Nasal wrappers for setting/getting clipboard text
*/
//------------------------------------------------------------------------------
static NasalClipboard::Type parseType(const nasal::CallContext& ctx, size_t i)
{
if( ctx.argc > i )
{
if( ctx.isNumeric(i) )
{
if( ctx.requireArg<int>(i) == NasalClipboard::CLIPBOARD )
return NasalClipboard::CLIPBOARD;
if( ctx.requireArg<int>(i) == NasalClipboard::PRIMARY )
return NasalClipboard::PRIMARY;
}
ctx.runtimeError("clipboard: invalid arg "
"(expected clipboard.CLIPBOARD or clipboard.SELECTION)");
}
return NasalClipboard::CLIPBOARD;
}
//------------------------------------------------------------------------------
static naRef f_setClipboardText(const nasal::CallContext& ctx)
{
if( ctx.argc < 1 || ctx.argc > 2 )
ctx.runtimeError("clipboard.setText() expects 1 or 2 arguments: "
"text, [, type = clipboard.CLIPBOARD]");
return
naNum
(
NasalClipboard::getInstance()->setText( ctx.requireArg<std::string>(0),
parseType(ctx, 1) )
);
}
//------------------------------------------------------------------------------
static naRef f_getClipboardText(const nasal::CallContext& ctx)
{
if( ctx.argc > 1 )
ctx.runtimeError("clipboard.getText() accepts max 1 arg: "
"[type = clipboard.CLIPBOARD]");
return ctx.to_nasal
(
NasalClipboard::getInstance()->getText(parseType(ctx, 0))
);
}
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::_clipboard;
//------------------------------------------------------------------------------
NasalClipboard::~NasalClipboard()
{
}
//------------------------------------------------------------------------------
void NasalClipboard::init(FGNasalSys *nasal)
{
_clipboard = create();
nasal::Hash clipboard = nasal->getGlobals().createHash("clipboard");
clipboard.set("setText", f_setClipboardText);
clipboard.set("getText", f_getClipboardText);
clipboard.set("CLIPBOARD", NasalClipboard::CLIPBOARD);
clipboard.set("SELECTION", NasalClipboard::PRIMARY);
}
//------------------------------------------------------------------------------
NasalClipboard::Ptr NasalClipboard::getInstance()
{
return _clipboard;
}

View File

@@ -0,0 +1,72 @@
// Clipboard access for Nasal
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef NASAL_CLIPOARD_HXX_
#define NASAL_CLIPOARD_HXX_
#include <simgear/nasal/nasal.h>
#include <memory>
#include <string>
class FGNasalSys;
class NasalClipboard
{
public:
enum Type
{
/// Standard clipboard as supported by nearly all operating systems
CLIPBOARD,
/// X11 platforms support also a mode called PRIMARY selection which
/// contains the current (mouse) selection and can typically be inserted
/// via a press on the middle mouse button
PRIMARY
};
typedef std::shared_ptr<NasalClipboard> Ptr;
virtual void update() {}
virtual std::string getText(Type type = CLIPBOARD) = 0;
virtual bool setText( const std::string& text,
Type type = CLIPBOARD ) = 0;
/**
* Sets up the clipboard and puts all the extension functions into a new
* "clipboard" namespace.
*/
static void init(FGNasalSys *nasal);
/**
* Get clipboard platform specific instance
*/
static Ptr getInstance();
protected:
static Ptr _clipboard;
/**
* Implementation supplied by actual platform implementation
*/
static Ptr create();
virtual ~NasalClipboard() = 0;
};
#endif /* NASAL_CLIPOARD_HXX_ */

View File

@@ -0,0 +1,69 @@
// NasalCondition -- expose SGCondition to Nasal
//
// Written by James Turner, started 2012.
//
// Copyright (C) 2012 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalCondition.hxx"
#include "NasalSys.hxx"
#include <Main/globals.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/props/condition.hxx>
typedef nasal::Ghost<SGConditionRef> NasalCondition;
//------------------------------------------------------------------------------
static naRef f_createCondition(naContext c, naRef me, int argc, naRef* args)
{
SGPropertyNode* node = argc > 0
? ghostToPropNode(args[0])
: NULL;
SGPropertyNode* root = argc > 1
? ghostToPropNode(args[1])
: globals->get_props();
if( !node || !root )
naRuntimeError(c, "createCondition: invalid argument(s)");
try
{
return nasal::to_nasal(c, sgReadCondition(root, node));
}
catch(std::exception& ex)
{
naRuntimeError(c, "createCondition: %s", ex.what());
}
return naNil();
}
//------------------------------------------------------------------------------
naRef initNasalCondition(naRef globals, naContext c)
{
nasal::Ghost<SGConditionRef>::init("Condition")
.method("test", &SGCondition::test);
nasal::Hash(globals, c).set("_createCondition", f_createCondition);
return naNil();
}

View File

@@ -0,0 +1,28 @@
// NasalCondition.hxx -- expose SGCondition to Nasal
//
// Written by James Turner, started 2012.
//
// Copyright (C) 2012 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_CONDITION_HXX
#define SCRIPTING_NASAL_CONDITION_HXX
#include <simgear/nasal/nasal.h>
naRef initNasalCondition(naRef globals, naContext c);
#endif // of SCRIPTING_NASAL_CONDITION_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
// NasalFlightPlan.hxx -- expose FlightPlan classes to Nasal
//
// Written by James Turner, started 2020.
//
// Copyright (C) 2020 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#pragma once
#include <simgear/nasal/nasal.h>
#include <Navaids/FlightPlan.hxx>
#include <Navaids/procedure.hxx>
flightgear::Waypt* wayptGhost(naRef r);
flightgear::FlightPlan::Leg* fpLegGhost(naRef r);
flightgear::Procedure* procedureGhost(naRef r);
naRef ghostForWaypt(naContext c, const flightgear::Waypt* wpt);
naRef ghostForLeg(naContext c, const flightgear::FlightPlan::Leg* leg);
naRef ghostForProcedure(naContext c, const flightgear::Procedure* proc);
naRef initNasalFlightPlan(naRef globals, naContext c);
void shutdownNasalFlightPlan();

124
src/Scripting/NasalHTTP.cxx Normal file
View File

@@ -0,0 +1,124 @@
// Expose HTTP module to Nasal
//
// Copyright (C) 2013 Thomas Geymayer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalHTTP.hxx"
#include <Main/globals.hxx>
#include <Network/HTTPClient.hxx>
#include <simgear/io/HTTPFileRequest.hxx>
#include <simgear/io/HTTPMemoryRequest.hxx>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
typedef nasal::Ghost<simgear::HTTP::Request_ptr> NasalRequest;
typedef nasal::Ghost<simgear::HTTP::FileRequestRef> NasalFileRequest;
typedef nasal::Ghost<simgear::HTTP::MemoryRequestRef> NasalMemoryRequest;
FGHTTPClient& requireHTTPClient(const nasal::ContextWrapper& ctx)
{
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
if( !http )
ctx.runtimeError("Failed to get HTTP subsystem");
return *http;
}
/**
* http.save(url, filename)
*/
static naRef f_http_save(const nasal::CallContext& ctx)
{
const std::string url = ctx.requireArg<std::string>(0);
// Check for write access to target file
const std::string filename = ctx.requireArg<std::string>(1);
const SGPath validated_path = SGPath(filename).validate(true);
if( validated_path.isNull() )
ctx.runtimeError("Access denied: can not write to %s", filename.c_str());
return ctx.to_nasal
(
requireHTTPClient(ctx).client()->save(url, validated_path.utf8Str())
);
}
/**
* http.load(url)
*/
static naRef f_http_load(const nasal::CallContext& ctx)
{
const std::string url = ctx.requireArg<std::string>(0);
return ctx.to_nasal( requireHTTPClient(ctx).client()->load(url) );
}
static naRef f_request_abort( simgear::HTTP::Request&,
const nasal::CallContext& ctx )
{
// we need a request_ptr for cancel, not a reference. So extract
// the me object from the context directly.
simgear::HTTP::Request_ptr req = ctx.from_nasal<simgear::HTTP::Request_ptr>(ctx.me);
requireHTTPClient(ctx).client()->cancelRequest(req);
return naNil();
}
//------------------------------------------------------------------------------
naRef initNasalHTTP(naRef globals, naContext c)
{
using simgear::HTTP::Request;
typedef Request* (Request::*HTTPCallback)(const Request::Callback&);
NasalRequest::init("http.Request")
.member("url", &Request::url)
.member("method", &Request::method)
.member("scheme", &Request::scheme)
.member("path", &Request::path)
.member("host", &Request::host)
.member("port", &Request::port)
.member("query", &Request::query)
.member("status", &Request::responseCode)
.member("reason", &Request::responseReason)
.member("readyState", &Request::readyState)
.method("abort", f_request_abort)
.method("done", static_cast<HTTPCallback>(&Request::done))
.method("fail", static_cast<HTTPCallback>(&Request::fail))
.method("always", static_cast<HTTPCallback>(&Request::always));
using simgear::HTTP::FileRequest;
NasalFileRequest::init("http.FileRequest")
.bases<NasalRequest>();
using simgear::HTTP::MemoryRequest;
NasalMemoryRequest::init("http.MemoryRequest")
.bases<NasalRequest>()
.member("response", &MemoryRequest::responseBody);
nasal::Hash globals_module(globals, c),
http = globals_module.createHash("http");
http.set("save", f_http_save);
http.set("load", f_http_load);
return naNil();
}

View File

@@ -0,0 +1,26 @@
//@file Expose HTTP module to Nasal
//
// Copyright (C) 2013 Thomas Geymayer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_HTTP_HXX
#define SCRIPTING_NASAL_HTTP_HXX
#include <simgear/nasal/nasal.h>
naRef initNasalHTTP(naRef globals, naContext c);
#endif // of SCRIPTING_NASAL_HTTP_HXX

View File

@@ -0,0 +1,229 @@
#include "NasalModelData.hxx"
#include "NasalSys.hxx"
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <osg/Transform>
#include <osg/observer_ptr>
#include <simgear/math/SGMath.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/scene/util/OsgDebug.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/debug/logstream.hxx>
#include <algorithm>
#include <cstring> // for strlen
// FGNasalModelData class. If sgLoad3DModel() is called with a pointer to
// 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.
unsigned int FGNasalModelData::_max_module_id = 0;
FGNasalModelDataList FGNasalModelData::_loaded_models;
typedef osg::ref_ptr<osg::Node> NodeRef;
typedef nasal::Ghost<NodeRef> NasalNode;
/**
* Get position (lat, lon, elevation) and orientation (heading, pitch, roll) of
* model.
*/
static naRef f_node_getPose( const osg::Node& node,
const nasal::CallContext& ctx )
{
osg::NodePathList parent_paths = node.getParentalNodePaths();
for( osg::NodePathList::const_iterator path = parent_paths.begin();
path != parent_paths.end();
++path )
{
osg::Matrix local_to_world = osg::computeLocalToWorld(*path);
if( !local_to_world.valid() )
continue;
SGGeod coord = SGGeod::fromCart( toSG(local_to_world.getTrans()) );
if( !coord.isValid() )
continue;
osg::Matrix local_frame = makeZUpFrameRelative(coord),
inv_local;
inv_local.invert_4x3(local_frame);
local_to_world.postMult(inv_local);
SGQuatd rotate = toSG(local_to_world.getRotate());
double hdg, pitch, roll;
rotate.getEulerDeg(hdg, pitch, roll);
nasal::Hash pose(ctx.to_nasal(coord), ctx.c_ctx());
pose.set("heading", hdg);
pose.set("pitch", pitch);
pose.set("roll", roll);
return pose.get_naRef();
}
return naNil();
}
//------------------------------------------------------------------------------
FGNasalModelData::FGNasalModelData( SGPropertyNode *root,
const std::string& path,
SGPropertyNode *prop,
SGPropertyNode* load,
SGPropertyNode* unload,
osg::Node* branch ):
_path(path),
_root(root), _prop(prop),
_load(load), _unload(unload),
_branch(branch),
_module_id( _max_module_id++ )
{
_loaded_models.push_back(this);
SG_LOG
(
SG_NASAL,
SG_INFO,
"New model with attached script(s) "
"(branch = " << branch << ","
" path = " << simgear::getNodePathString(branch) << ")"
);
}
//------------------------------------------------------------------------------
FGNasalModelData::~FGNasalModelData()
{
_loaded_models.remove(this);
SG_LOG
(
SG_NASAL,
SG_INFO,
"Removed model with script(s) (branch = " << _branch.get() << ")"
);
}
//------------------------------------------------------------------------------
void FGNasalModelData::load()
{
std::stringstream m;
m << "__model" << _module_id;
_module = m.str();
SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str());
const string s = _load ? _load->getStringValue() : "";
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
// Add _module_id to script local hash to allow placing canvasses on objects
// inside the model.
nasal::Hash module = nasalSys->getGlobals().createHash(_module);
module.set("_module_id", _module_id);
NasalNode::init("osg.Node")
.method("getPose", &f_node_getPose);
module.set("_model", _branch);
naRef arg[2];
arg[0] = nasalSys->propNodeGhost(_root);
arg[1] = nasalSys->propNodeGhost(_prop);
nasalSys->createModule(_module.c_str(), _path.c_str(), s.c_str(), s.length(),
_root, 2, arg);
}
//------------------------------------------------------------------------------
void FGNasalModelData::unload()
{
if (_module.empty())
return;
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
if(!nasalSys) {
SG_LOG(SG_NASAL, SG_WARN, "Trying to run an <unload> script "
"without Nasal subsystem present.");
return;
}
SG_LOG(SG_NASAL, SG_DEBUG, "Unloading nasal module " << _module.c_str());
if (_unload)
{
const string s = _unload->getStringValue();
nasalSys->createModule(_module.c_str(), _module.c_str(), s.c_str(), s.length(), _root);
}
nasalSys->deleteModule(_module.c_str());
}
//------------------------------------------------------------------------------
osg::Node* FGNasalModelData::getNode()
{
return _branch.get();
}
//------------------------------------------------------------------------------
FGNasalModelData* FGNasalModelData::getByModuleId(unsigned int id)
{
FGNasalModelDataList::iterator it = std::find_if
(
_loaded_models.begin(),
_loaded_models.end(),
[id] (const FGNasalModelData* const data) {
return data->_module_id == id; });
if( it != _loaded_models.end() )
return *it;
return 0;
}
//------------------------------------------------------------------------------
FGNasalModelDataProxy::~FGNasalModelDataProxy()
{
auto nasalSys = globals->get_subsystem<FGNasalSys>();
// when necessary, register Nasal module to be destroyed/unloaded
// in the main thread.
if ((_data.valid())&&(nasalSys))
nasalSys->registerToUnload(_data);
}
//------------------------------------------------------------------------------
void FGNasalModelDataProxy::modelLoaded( const std::string& path,
SGPropertyNode *prop,
osg::Node *branch )
{
if( fgGetBool("/sim/disable-embedded-nasal") )
return;
if(!prop)
return;
SGPropertyNode *nasal = prop->getNode("nasal");
if(!nasal)
return;
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
if(!nasalSys)
{
SG_LOG
(
SG_NASAL,
SG_WARN,
"Can not load model script(s) (Nasal subsystem not available)."
);
return;
}
SGPropertyNode* load = nasal->getNode("load");
SGPropertyNode* unload = nasal->getNode("unload");
if ((!load) && (!unload))
return;
_data = new FGNasalModelData(_root, path, prop, load, unload, branch);
// register Nasal module to be created and loaded in the main thread.
nasalSys->registerToLoad(_data);
}

View File

@@ -0,0 +1,104 @@
// Copyright (C) 2013 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef NASAL_MODEL_DATA_HXX
#define NASAL_MODEL_DATA_HXX
#include <simgear/nasal/nasal.h>
#include <simgear/scene/model/modellib.hxx>
class FGNasalModelData;
typedef SGSharedPtr<FGNasalModelData> FGNasalModelDataRef;
typedef std::list<FGNasalModelData*> FGNasalModelDataList;
/** Nasal model data container.
* load and unload methods must be run in main thread (not thread-safe). */
class FGNasalModelData : public SGReferenced
{
public:
/** Constructor to be run in an arbitrary thread. */
FGNasalModelData( SGPropertyNode *root,
const std::string& path,
SGPropertyNode *prop,
SGPropertyNode* load,
SGPropertyNode* unload,
osg::Node* branch );
~FGNasalModelData();
/** Load hook. Always call from inside the main loop. */
void load();
/** Unload hook. Always call from inside the main loop. */
void unload();
/**
* Get osg scenegraph node of model
*/
osg::Node* getNode();
/**
* Get FGNasalModelData for model with the given module id. Every scenery
* model containing a nasal load or unload tag gets assigned a module id
* automatically.
*
* @param id Module id
* @return model data or NULL if does not exists
*/
static FGNasalModelData* getByModuleId(unsigned int id);
private:
static unsigned int _max_module_id;
static FGNasalModelDataList _loaded_models;
std::string _module, _path;
SGPropertyNode_ptr _root, _prop;
SGConstPropertyNode_ptr _load, _unload;
osg::ref_ptr<osg::Node> _branch;
unsigned int _module_id;
};
/** Thread-safe proxy for FGNasalModelData.
* modelLoaded/destroy methods only register the requested
* operation. Actual (un)loading of Nasal module is deferred
* and done in the main loop. */
class FGNasalModelDataProxy : public simgear::SGModelData
{
public:
FGNasalModelDataProxy(SGPropertyNode *root = 0) :
_root(root), _data(0)
{
}
~FGNasalModelDataProxy();
void modelLoaded( const std::string& path,
SGPropertyNode *prop,
osg::Node *branch );
virtual FGNasalModelDataProxy* clone() const { return new FGNasalModelDataProxy(_root); }
ErrorContext getErrorContext() const override
{
return {}; // return nothing for now, not yet clear if this proxy needs it
}
protected:
SGPropertyNode_ptr _root;
FGNasalModelDataRef _data;
};
#endif // of NASAL_MODEL_DATA_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
// NasalPositioned.hxx -- expose FGPositioned classes to Nasal
//
// Written by James Turner, started 2012.
//
// Copyright (C) 2012 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_POSITIONED_HXX
#define SCRIPTING_NASAL_POSITIONED_HXX
#include <simgear/nasal/nasal.h>
#include <Navaids/positioned.hxx>
// forward decls
class SGGeod;
class FGRunway;
class FGAirport;
bool geodFromHash(naRef ref, SGGeod& result);
FGAirport* airportGhost(naRef r);
FGRunway* runwayGhost(naRef r);
naRef ghostForPositioned(naContext c, FGPositionedRef pos);
naRef ghostForRunway(naContext c, const FGRunway* r);
naRef ghostForAirport(naContext c, const FGAirport* apt);
FGPositioned* positionedGhost(naRef r);
FGPositionedRef positionedFromArg(naRef ref);
int geodFromArgs(naRef* args, int offset, int argc, SGGeod& result);
naRef initNasalPositioned(naRef globals, naContext c);
naRef initNasalPositioned_cppbind(naRef globals, naContext c);
void postinitNasalPositioned(naRef globals, naContext c);
void shutdownNasalPositioned();
#endif // of SCRIPTING_NASAL_POSITIONED_HXX

View File

@@ -0,0 +1,536 @@
// NasalPositioned_cppbind.cxx -- expose FGPositioned classes to Nasal
//
// Port of NasalPositioned.cpp to the new nasal/cppbind helpers. Will replace
// old NasalPositioned.cpp once finished.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalPositioned.hxx"
#include <algorithm>
#include <functional>
#include <simgear/misc/ListDiff.hxx>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <Airports/airport.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/pavement.hxx>
#include <ATC/CommStation.hxx>
#include <Main/globals.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navlist.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/fix.hxx>
typedef nasal::Ghost<FGPositionedRef> NasalPositioned;
typedef nasal::Ghost<FGRunwayRef> NasalRunway;
typedef nasal::Ghost<FGParkingRef> NasalParking;
typedef nasal::Ghost<FGAirportRef> NasalAirport;
typedef nasal::Ghost<flightgear::CommStationRef> NasalCommStation;
typedef nasal::Ghost<FGNavRecordRef> NasalNavRecord;
typedef nasal::Ghost<FGRunwayRef> NasalRunway;
typedef nasal::Ghost<FGFixRef> NasalFix;
//------------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, flightgear::SID* sid)
{
// TODO SID ghost
return nasal::to_nasal(c, sid->ident());
}
//------------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, flightgear::STAR* star)
{
// TODO STAR ghost
return nasal::to_nasal(c, star->ident());
}
//------------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, flightgear::Approach* iap)
{
// TODO Approach ghost
return nasal::to_nasal(c, iap->ident());
}
//------------------------------------------------------------------------------
static naRef f_navaid_course(FGNavRecord& nav, naContext)
{
if( !( nav.type() == FGPositioned::ILS
|| nav.type() == FGPositioned::LOC
) )
return naNil();
double radial = nav.get_multiuse();
return naNum(SGMiscd::normalizePeriodic(0.5, 360.5, radial));
}
//------------------------------------------------------------------------------
static FGRunwayBaseRef f_airport_runway(FGAirport& apt, const std::string& ident)
{
const std::string Id = simgear::strutils::uppercase (ident);
if( apt.hasRunwayWithIdent(Id) )
return apt.getRunwayByIdent(Id);
else if( apt.hasHelipadWithIdent(Id) )
return apt.getHelipadByIdent(Id);
return 0;
}
//------------------------------------------------------------------------------
template<class T, class C1, class C2>
std::vector<T> extract( const std::vector<C1>& in,
T (C2::*getter)() const )
{
std::vector<T> ret(in.size());
std::transform(in.begin(), in.end(), ret.begin(), [getter](const C1& c)
{ return (c->*getter)(); }
);
return ret;
}
//------------------------------------------------------------------------------
static naRef f_airport_comms(FGAirport& apt, const nasal::CallContext& ctx)
{
FGPositioned::Type comm_type =
FGPositioned::typeFromName( ctx.getArg<std::string>(0) );
// if we have an explicit type, return a simple vector of frequencies
if( comm_type != FGPositioned::INVALID )
return ctx.to_nasal
(
extract( apt.commStationsOfType(comm_type),
&flightgear::CommStation::freqMHz )
);
else
// otherwise return a vector of ghosts, one for each comm station.
return ctx.to_nasal(apt.commStations());
}
//------------------------------------------------------------------------------
FGRunwayRef runwayFromNasalArg( const FGAirport& apt,
const nasal::CallContext& ctx,
size_t index = 0 )
{
if( index >= ctx.argc )
return FGRunwayRef();
try
{
std::string ident = ctx.getArg<std::string>(index);
if( !ident.empty() )
{
if( !apt.hasRunwayWithIdent(ident) )
// TODO warning/exception?
return FGRunwayRef();
return apt.getRunwayByIdent(ident);
}
}
catch(...)
{}
// TODO warn/error if no runway?
return ctx.from_nasal<FGRunwayRef>(ctx.args[index]);
}
//------------------------------------------------------------------------------
static naRef f_airport_sids(FGAirport& apt, const nasal::CallContext& ctx)
{
FGRunway* rwy = runwayFromNasalArg(apt, ctx);
return ctx.to_nasal
(
extract(rwy ? rwy->getSIDs() : apt.getSIDs(), &flightgear::SID::ident)
);
}
//------------------------------------------------------------------------------
static naRef f_airport_stars(FGAirport& apt, const nasal::CallContext& ctx)
{
FGRunway* rwy = runwayFromNasalArg(apt, ctx);
return ctx.to_nasal
(
extract(rwy ? rwy->getSTARs() : apt.getSTARs(), &flightgear::STAR::ident)
);
}
//------------------------------------------------------------------------------
static naRef f_airport_approaches(FGAirport& apt, const nasal::CallContext& ctx)
{
FGRunway* rwy = runwayFromNasalArg(apt, ctx);
flightgear::ProcedureType type = flightgear::PROCEDURE_INVALID;
const std::string type_str = simgear::strutils::uppercase (ctx.getArg<std::string>(1));
if( !type_str.empty() )
{
if( type_str == "NDB" ) type = flightgear::PROCEDURE_APPROACH_NDB;
else if( type_str == "VOR" ) type = flightgear::PROCEDURE_APPROACH_VOR;
else if( type_str == "ILS" ) type = flightgear::PROCEDURE_APPROACH_ILS;
else if( type_str == "RNAV") type = flightgear::PROCEDURE_APPROACH_RNAV;
}
return ctx.to_nasal
(
extract( rwy ? rwy->getApproaches(type)
// no runway specified, report them all
: apt.getApproaches(type),
&flightgear::Approach::ident )
);
}
//------------------------------------------------------------------------------
static FGParkingList
f_airport_parking(FGAirport& apt, nasal::CallContext ctx)
{
std::string type = ctx.getArg<std::string>(0);
bool only_available = ctx.getArg<bool>(1);
FGAirportDynamicsRef dynamics = apt.getDynamics();
return dynamics->getParkings(only_available, type);
}
/**
* Extract a SGGeod from a nasal function argument list.
*
* <lat>, <lon>
* {"lat": <lat-deg>, "lon": <lon-deg>}
* geo.Coord.new() (aka. {"_lat": <lat-rad>, "_lon": <lon-rad>})
*/
static bool extractGeod(nasal::CallContext& ctx, SGGeod& result)
{
if( !ctx.argc )
return false;
if( ctx.isGhost(0) )
{
FGPositionedRef pos =
ctx.from_nasal<FGPositionedRef>(ctx.requireArg<naRef>(0));
if( pos )
{
result = pos->geod();
ctx.popFront();
return true;
}
}
else if( ctx.isHash(0) )
{
nasal::Hash pos_hash = ctx.requireArg<nasal::Hash>(0);
// check for manual latitude / longitude names
naRef lat = pos_hash.get("lat"),
lon = pos_hash.get("lon");
if( naIsNum(lat) && naIsNum(lon) )
{
result = SGGeod::fromDeg( ctx.from_nasal<double>(lon),
ctx.from_nasal<double>(lat) );
ctx.popFront();
return true;
}
// geo.Coord uses _lat/_lon in radians
// TODO should we check if its really a geo.Coord?
lat = pos_hash.get("_lat");
lon = pos_hash.get("_lon");
if( naIsNum(lat) && naIsNum(lon) )
{
result = SGGeod::fromRad( ctx.from_nasal<double>(lon),
ctx.from_nasal<double>(lat) );
ctx.popFront();
return true;
}
}
else if( ctx.isNumeric(0) && ctx.isNumeric(1) )
{
// lat, lon
result = SGGeod::fromDeg( ctx.requireArg<double>(1),
ctx.requireArg<double>(0) );
ctx.popFront(2);
return true;
}
return false;
}
/**
* Extract position from ctx or return current aircraft position if not given.
*/
static SGGeod getPosition(nasal::CallContext& ctx)
{
SGGeod pos;
if( !extractGeod(ctx, pos) )
pos = globals->get_aircraft_position();
return pos;
}
//------------------------------------------------------------------------------
// Returns Nasal ghost for particular or nearest airport of a <type>, or nil
// on error.
//
// airportinfo(<id>); e.g. "KSFO"
// airportinfo(<type>); type := ("airport"|"seaport"|"heliport")
// airportinfo() same as airportinfo("airport")
// airportinfo(<lat>, <lon> [, <type>]);
static naRef f_airportinfo(nasal::CallContext ctx)
{
SGGeod pos = getPosition(ctx);
if( ctx.argc > 1 )
ctx.runtimeError("airportinfo() with invalid function arguments");
// optional type/ident
std::string ident("airport");
if( ctx.isString(0) )
ident = ctx.requireArg<std::string>(0);
FGAirport::TypeRunwayFilter filter;
if( !filter.fromTypeString(ident) )
// user provided an <id>, hopefully
return ctx.to_nasal(FGAirport::findByIdent(ident));
double maxRange = 10000.0; // expose this? or pick a smaller value?
return ctx.to_nasal( FGAirport::findClosest(pos, maxRange, &filter) );
}
/**
* findAirportsWithinRange([<position>,] <range-nm> [, type])
*/
static naRef f_findAirportsWithinRange(nasal::CallContext ctx)
{
SGGeod pos = getPosition(ctx);
double range_nm = ctx.requireArg<double>(0);
FGAirport::TypeRunwayFilter filter; // defaults to airports only
filter.fromTypeString( ctx.getArg<std::string>(1) );
FGPositionedList apts = FGPositioned::findWithinRange(pos, range_nm, &filter);
FGPositioned::sortByRange(apts, pos);
return ctx.to_nasal(apts);
}
/**
* findAirportsByICAO(<ident/prefix> [, type])
*/
static naRef f_findAirportsByICAO(nasal::CallContext ctx)
{
std::string prefix = ctx.requireArg<std::string>(0);
FGAirport::TypeRunwayFilter filter; // defaults to airports only
filter.fromTypeString( ctx.getArg<std::string>(1) );
return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, false) );
}
// 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(nasal::CallContext ctx)
{
SGGeod pos = getPosition(ctx);
std::string id = ctx.getArg<std::string>(0);
FGNavList::TypeFilter filter;
if( filter.fromTypeString(id) )
id = ctx.getArg<std::string>(1);
else if( ctx.argc > 1 )
ctx.runtimeError("navinfo() already got an ident");
return ctx.to_nasal( FGNavList::findByIdentAndFreq(pos, id, 0.0, &filter) );
}
//------------------------------------------------------------------------------
static naRef f_findWithinRange(nasal::CallContext ctx)
{
SGGeod pos = getPosition(ctx);
double range_nm = ctx.requireArg<double>(0);
std::string typeSpec = ctx.getArg<std::string>(1);
FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
FGPositionedList items = FGPositioned::findWithinRange(pos, range_nm, &filter);
FGPositioned::sortByRange(items, pos);
return ctx.to_nasal(items);
}
static naRef f_findByIdent(nasal::CallContext ctx)
{
std::string prefix = ctx.requireArg<std::string>(0);
std::string typeSpec = ctx.getArg<std::string>(1);
FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
bool exact = ctx.getArg<bool>(2, false);
return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, exact) );
}
static naRef f_findByName(nasal::CallContext ctx)
{
std::string prefix = ctx.requireArg<std::string>(0);
std::string typeSpec = ctx.getArg<std::string>(1);
FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
return ctx.to_nasal( FGPositioned::findAllWithName(prefix, &filter, false) );
}
//------------------------------------------------------------------------------
static naRef f_courseAndDistance(nasal::CallContext ctx)
{
SGGeod from = globals->get_aircraft_position(), to, pos;
bool ok = extractGeod(ctx, pos);
if (!ok) {
ctx.runtimeError("invalid arguments to courseAndDistance");
}
if (extractGeod(ctx, to)) {
from = pos; // we parsed both FROM and TO args, so first was FROM
} else {
to = pos; // only parsed one arg, so FROM is current
}
double course, course2, d;
SGGeodesy::inverse(from, to, course, course2, d);
return ctx.to_nasal_vec(course, d * SG_METER_TO_NM);
}
static naRef f_sortByRange(nasal::CallContext ctx)
{
FGPositionedList items = ctx.requireArg<FGPositionedList>(0);
ctx.popFront();
FGPositioned::sortByRange(items, getPosition(ctx));
return ctx.to_nasal(items);
}
//------------------------------------------------------------------------------
// Get difference between two lists of positioned objects.
//
// For every element in old_list not in new_list the callback cb_remove is
// called with the removed element as single argument. For every element in
// new_list not in old_list cb_add is called.
//
// diff(old_list, new_list, cb_add[, cb_remove])
//
// example:
// # Print all fixes within a distance of 320 to 640 miles
// diff( findWithinRange(320, "fix"),
// findWithinRange(640, "fix"),
// func(p) print('found fix: ', p.id) );
static naRef f_diff(nasal::CallContext ctx)
{
typedef simgear::ListDiff<FGPositionedRef> Diff;
Diff::List old_list = ctx.requireArg<FGPositionedList>(0),
new_list = ctx.requireArg<FGPositionedList>(1);
Diff::Callback cb_add = ctx.requireArg<Diff::Callback>(2),
cb_rm = ctx.getArg<Diff::Callback>(3);
// Note that FGPositionedRef instances are only compared for pointer equality.
// As the NavCache caches every queried positioned instance it is guaranteed
// that only one instance of every positioned object can exist. Therefore we
// can make the comparison faster by just comparing pointers and not also the
// guid.
// (On my machine the difference is 0.27s vs 0.17s)
Diff::inplace(old_list, new_list, cb_add, cb_rm);
return naNil();
}
//------------------------------------------------------------------------------
naRef initNasalPositioned_cppbind(naRef globalsRef, naContext c)
{
NasalPositioned::init("Positioned")
.member("id", &FGPositioned::ident)
.member("ident", &FGPositioned::ident) // TODO to we really need id and ident?
.member("name", &FGPositioned::name)
.member("type", &FGPositioned::typeString)
.member("lat", &FGPositioned::latitude)
.member("lon", &FGPositioned::longitude)
.member("elevation", &FGPositioned::elevationM);
NasalRunway::init("Runway")
.bases<NasalPositioned>();
NasalParking::init("Parking")
.bases<NasalPositioned>();
NasalCommStation::init("CommStation")
.bases<NasalPositioned>()
.member("frequency", &flightgear::CommStation::freqMHz);
NasalNavRecord::init("Navaid")
.bases<NasalPositioned>()
.member("frequency", &FGNavRecord::get_freq)
.member("range_nm", &FGNavRecord::get_range)
.member("course", &f_navaid_course)
.member("magvar", &FGNavRecord::get_multiuse)
.member("dme", &FGNavRecord::hasDME)
.member("vorac", &FGNavRecord::isVORTAC);
NasalFix::init("Fix")
.bases<NasalPositioned>();
NasalAirport::init("FGAirport")
.bases<NasalPositioned>()
.member("has_metar", &FGAirport::getMetar)
.member("runways", &FGAirport::getRunwayMap)
.member("helipads", &FGAirport::getHelipadMap)
.member("taxiways", &FGAirport::getTaxiways)
.member("pavements", &FGAirport::getPavements)
.method("runway", &f_airport_runway)
.method("helipad", &f_airport_runway)
.method("tower", &FGAirport::getTowerLocation)
.method("comms", &f_airport_comms)
.method("sids", &f_airport_sids)
.method("stars", &f_airport_stars)
.method("getApproachList", f_airport_approaches)
.method("parking", &f_airport_parking)
.method("getSid", &FGAirport::findSIDWithIdent)
.method("getStar", &FGAirport::findSTARWithIdent)
.method("getIAP", &FGAirport::findApproachWithIdent)
.method("tostring", &FGAirport::toString);
nasal::Hash globals(globalsRef, c),
positioned( globals.createHash("positioned") );
positioned.set("airportinfo", &f_airportinfo);
positioned.set("findAirportsWithinRange", f_findAirportsWithinRange);
positioned.set("findAirportsByICAO", &f_findAirportsByICAO);
positioned.set("navinfo", &f_navinfo);
positioned.set("findWithinRange", &f_findWithinRange);
positioned.set("findByIdent", &f_findByIdent);
positioned.set("findByName", &f_findByName);
positioned.set("courseAndDistance", &f_courseAndDistance);
positioned.set("sortByRange", &f_sortByRange);
positioned.set("diff", &f_diff);
return naNil();
}

View File

@@ -0,0 +1,152 @@
// Expose SGPath module to Nasal
//
// Copyright (C) 2013 The FlightGear Community
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalSGPath.hxx"
#include <Main/globals.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
typedef std::shared_ptr<SGPath> SGPathRef;
typedef nasal::Ghost<SGPathRef> NasalSGPath;
SGPath::Permissions checkIORules(const SGPath& path)
{
SGPath::Permissions perm;
if (!path.isAbsolute()) {
// SGPath caches permissions, which breaks for relative paths
// if the current directory changes
SG_LOG(SG_NASAL, SG_ALERT, "os.path: file operation on '" <<
path<< "' access denied (relative paths not accepted; use "
"realpath() to make a path absolute)");
}
perm.read = path.isAbsolute() && !SGPath(path).validate(false).isNull();
perm.write = path.isAbsolute() && !SGPath(path).validate(true).isNull();
return perm;
}
// TODO make exposing such function easier...
static naRef validatedPathToNasal( const nasal::CallContext& ctx,
const SGPath& p )
{
return ctx.to_nasal( SGPathRef(new SGPath(p.utf8Str(), &checkIORules)) );
}
/**
* os.path.new()
*/
static naRef f_new_path(const nasal::CallContext& ctx)
{
return validatedPathToNasal(ctx, SGPath(ctx.getArg<std::string>(0)));
}
static int f_path_create_dir(SGPath& p, const nasal::CallContext& ctx)
{
// limit setable access rights for Nasal
return p.create_dir(ctx.getArg<mode_t>(0, 0755) & 0775);
}
static void f_path_set(SGPath& p, const nasal::CallContext& ctx)
{
p = SGPath::fromUtf8(ctx.getArg<std::string>(0), p.getPermissionChecker());
}
/**
* os.path.desktop()
*/
static naRef f_desktop(const nasal::CallContext& ctx)
{
return validatedPathToNasal(ctx, SGPath::desktop(SGPath(&checkIORules)));
}
/**
* os.path.standardLocation(type)
*/
static naRef f_standardLocation(const nasal::CallContext& ctx)
{
const std::string type_str = ctx.requireArg<std::string>(0);
SGPath::StandardLocation type = SGPath::HOME;
if( type_str == "DESKTOP" )
type = SGPath::DESKTOP;
else if( type_str == "DOWNLOADS" )
type = SGPath::DOWNLOADS;
else if( type_str == "DOCUMENTS" )
type = SGPath::DOCUMENTS;
else if( type_str == "PICTURES" )
type = SGPath::PICTURES;
else if( type_str != "HOME" )
ctx.runtimeError
(
"os.path.standardLocation: unknown type %s", type_str.c_str()
);
return validatedPathToNasal(ctx, SGPath::standardLocation(type));
}
//------------------------------------------------------------------------------
naRef initNasalSGPath(naRef globals, naContext c)
{
// This wraps most of the SGPath APIs for use by Nasal
// See: http://docs.freeflightsim.org/simgear/classSGPath.html
NasalSGPath::init("os.path")
.method("set", &f_path_set)
.method("append", &SGPath::append)
.method("concat", &SGPath::concat)
.member("realpath", &SGPath::realpath)
.member("file", &SGPath::file)
.member("dir", &SGPath::dir)
.member("base", &SGPath::base)
.member("file_base", &SGPath::file_base)
.member("extension", &SGPath::extension)
.member("lower_extension", &SGPath::lower_extension)
.member("complete_lower_extension", &SGPath::complete_lower_extension)
.member("str", &SGPath::utf8Str)
.member("mtime", &SGPath::modTime)
.method("exists", &SGPath::exists)
.method("canRead", &SGPath::canRead)
.method("canWrite", &SGPath::canWrite)
.method("isFile", &SGPath::isFile)
.method("isDir", &SGPath::isDir)
.method("isRelative", &SGPath::isRelative)
.method("isAbsolute", &SGPath::isAbsolute)
.method("isNull", &SGPath::isNull)
.method("create_dir", &f_path_create_dir)
.method("remove", &SGPath::remove)
.method("rename", &SGPath::rename);
nasal::Hash globals_module(globals, c),
path = globals_module.createHash("os")
.createHash("path");
path.set("new", f_new_path);
path.set("desktop", &f_desktop);
path.set("standardLocation", &f_standardLocation);
return naNil();
}

View File

@@ -0,0 +1,26 @@
//@file Expose SGPath module to Nasal
//
// Copyright (C) 2013 The FlightGear Community
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_SGPATH_HXX
#define SCRIPTING_NASAL_SGPATH_HXX
#include <simgear/nasal/nasal.h>
naRef initNasalSGPath(naRef globals, naContext c);
#endif // of SCRIPTING_NASAL_SGPATH_HXX

View File

@@ -0,0 +1,140 @@
// Add (std::string) like methods to Nasal strings
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NasalString.hxx"
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/NasalString.hxx>
/**
* Compare (sub)string with other string
*
* compare(s)
* compare(pos, len, s)
*/
static naRef f_compare(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
rhs = ctx.requireArg<nasal::String>(ctx.argc > 1 ? 2 : 0);
size_t pos = ctx.argc > 1 ? ctx.requireArg<int>(1) : 0;
size_t len = ctx.argc > 1 ? ctx.requireArg<int>(2) : 0;
if( len == 0 )
len = nasal::String::npos;
return naNum( str.compare(pos, len, rhs) );
}
/**
* Check whether string starts with other string
*/
static naRef f_starts_with(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
rhs = ctx.requireArg<nasal::String>(0);
return naNum( str.starts_with(rhs) );
}
/**
* Check whether string ends with other string
*/
static naRef f_ends_with(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
rhs = ctx.requireArg<nasal::String>(0);
return naNum( str.ends_with(rhs) );
}
/**
* Helper to convert size_t position/npos to Nasal conventions (-1 == npos)
*/
naRef pos_to_nasal(size_t pos)
{
if( pos == nasal::String::npos )
return naNum(-1);
else
return naNum(pos);
}
/**
* Find first occurrence of single character
*
* find(c, pos = 0)
*/
static naRef f_find(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
find = ctx.requireArg<nasal::String>(0);
size_t pos = ctx.getArg<int>(1, 0);
if( find.size() != 1 )
ctx.runtimeError("string::find: single character expected");
return pos_to_nasal( str.find(*find.c_str(), pos) );
}
/**
* Find first character of a string occurring in this string
*
* find_first_of(search, pos = 0)
*/
static naRef f_find_first_of(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
find = ctx.requireArg<nasal::String>(0);
size_t pos = ctx.getArg<int>(1, 0);
return pos_to_nasal( str.find_first_of(find, pos) );
}
/**
* Find first character of this string not occurring in the other string
*
* find_first_not_of(search, pos = 0)
*/
static naRef f_find_first_not_of(const nasal::CallContext& ctx)
{
nasal::String str = ctx.from_nasal<nasal::String>(ctx.me),
find = ctx.requireArg<nasal::String>(0);
size_t pos = ctx.getArg<int>(1, 0);
return pos_to_nasal( str.find_first_not_of(find, pos) );
}
//------------------------------------------------------------------------------
naRef initNasalString(naRef globals, naRef string, naContext c)
{
nasal::Hash string_module(string, c);
string_module.set("compare", f_compare);
string_module.set("starts_with", f_starts_with);
string_module.set("ends_with", f_ends_with);
string_module.set("find", f_find);
string_module.set("find_first_of", f_find_first_of);
string_module.set("find_first_not_of", f_find_first_not_of);
return naNil();
}

View File

@@ -0,0 +1,27 @@
// Add (std::string) like methods to Nasal strings
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_STRING_HXX
#define SCRIPTING_NASAL_STRING_HXX
#include <simgear/nasal/nasal.h>
naRef initNasalString(naRef globals, naRef string, naContext c);
#endif // of SCRIPTING_NASAL_STRING_HXX

2075
src/Scripting/NasalSys.cxx Normal file

File diff suppressed because it is too large Load Diff

256
src/Scripting/NasalSys.hxx Normal file
View File

@@ -0,0 +1,256 @@
#ifndef __NASALSYS_HXX
#define __NASALSYS_HXX
#include <simgear/math/SGMath.hxx> // keep before any cppbind include to enable
// SGVec2<T> conversion.
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/nasal.h>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/threads/SGQueue.hxx>
// Required only for MSVC
#ifdef _MSC_VER
# include <Scripting/NasalModelData.hxx>
#endif
#include <map>
#include <memory>
class FGNasalScript;
class FGNasalListener;
class SGCondition;
class FGNasalModelData;
class NasalCommand;
class FGNasalModuleListener;
struct NasalTimer; ///< timer created by settimer
class TimerObj; ///< persistent timer created by maketimer
namespace simgear { class BufferedLogCallback; }
SGPropertyNode* ghostToPropNode(naRef ref);
class FGNasalSys : public SGSubsystem
{
public:
FGNasalSys();
virtual ~FGNasalSys();
// Subsystem API.
void init() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "nasal"; }
// Loads a nasal script from an external file and inserts it as a
// global module of the specified name.
bool loadModule(SGPath file, const char* moduleName);
// Simple hook to run arbitrary source code. Returns a bool to
// indicate successful execution. Does *not* return any Nasal
// values, because handling garbage-collected objects from C space
// is deep voodoo and violates the "simple hook" idea.
bool parseAndRun(const std::string& source);
bool parseAndRunWithOutput(const std::string& source,
std::string& output,
std::string& errors);
// Implementation of the settimer extension function
void setTimer(naContext c, int argc, naRef* args);
// Implementation of the setlistener extension function
naRef setListener(naContext c, int argc, naRef* args);
naRef removeListener(naContext c, int argc, naRef* args);
// Returns a ghost wrapper for the current _cmdArg
naRef cmdArgGhost();
void setCmdArg(SGPropertyNode* aNode);
/**
* create Nasal props.Node for an SGPropertyNode*
* This is the actual ghost, wrapped in a Nasal sugar class.
*/
naRef wrappedPropsNode(SGPropertyNode* aProps);
// Callbacks for command and timer bindings
virtual bool handleCommand( const char* moduleName,
const char* fileName,
const char* src,
const SGPropertyNode* arg = 0,
SGPropertyNode* root = 0);
virtual bool handleCommand(const SGPropertyNode* arg, SGPropertyNode *root);
bool createModule(const char* moduleName, const char* fileName,
const char* src, int len, const SGPropertyNode* cmdarg=0,
int argc=0, naRef*args=0);
void deleteModule(const char* moduleName);
naRef getModule(const std::string& moduleName) const;
naRef getModule(const char* moduleName);
bool addCommand(naRef func, const std::string& name);
bool removeCommand(const std::string& name);
/**
* Set member of specified hash to given value
*/
void hashset(naRef hash, const char* key, naRef val);
/**
* Set member of globals hash to given value
*/
void globalsSet(const char* key, naRef val);
naRef call(naRef code, int argc, naRef* args, naRef locals);
naRef callWithContext(naContext ctx, naRef code, int argc, naRef* args, naRef locals);
naRef callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals);
naRef callMethodWithContext(naContext ctx, naRef code, naRef self, int argc, naRef* args, naRef locals);
naRef propNodeGhost(SGPropertyNode* handle);
void registerToLoad(FGNasalModelData* data);
void registerToUnload(FGNasalModelData* data);
// can't call this 'globals' due to naming clash
naRef nasalGlobals() const
{ return _globals; }
nasal::Hash getGlobals() const
{ return nasal::Hash(_globals, _context); }
// This mechanism is here to allow naRefs to be passed to
// locations "outside" the interpreter. Normally, such a
// reference would be garbage collected unexpectedly. By passing
// it to gcSave and getting a key/handle, it can be cached in a
// globals.__gcsave hash. Be sure to release it with gcRelease
// when done.
int gcSave(naRef r);
void gcRelease(int key);
/**
* Check if IOrules correctly work to limit access from Nasal scripts to the
* file system.
*
* @note Just a simple test is performed to check if access to a path is
* possible which should never be possible (The actual path refers to
* a file/folder named 'do-not-access' in the file system root).
*
* @see http://wiki.flightgear.org/IOrules
*
* @return Whether the check was successful.
*/
bool checkIOrules();
/// retrive the associated log object, for displaying log
/// output somewhere (a UI, presumably)
simgear::BufferedLogCallback* log() const
{ return _log.get(); }
string_list getAndClearErrorList();
/**
@brief Convert the value of an SGPropertyNode to its Nasal representation. Used by
props.Node.getValue internally, but exposed here for other use cases which don't want to create
a props.Node wrapper each time.
*/
static naRef getPropertyValue(naContext c, SGPropertyNode* node);
private:
void initLogLevelConstants();
void loadPropertyScripts();
void loadPropertyScripts(SGPropertyNode* n);
void loadScriptDirectory(simgear::Dir nasalDir, SGPropertyNode* loadorder,
bool excludeUnspecifiedInLoadOrder);
void addModule(std::string moduleName, simgear::PathList scripts);
static void logError(naContext);
naRef parse(naContext ctx, const char* filename, const char* buf, int len,
std::string& errors);
naRef genPropsModule();
private:
//friend class FGNasalScript;
friend class FGNasalListener;
friend class FGNasalModuleListener;
SGLockedQueue<SGSharedPtr<FGNasalModelData> > _loadList;
SGLockedQueue<SGSharedPtr<FGNasalModelData> > _unloadList;
// Delay removing items of the _loadList to ensure the are already attached
// to the scene graph (eg. enables to retrieve world position in load
// callback).
bool _delay_load;
// Listener
std::map<int, FGNasalListener *> _listener;
std::vector<FGNasalListener *> _dead_listener;
std::vector<FGNasalModuleListener*> _moduleListeners;
static int _listenerId;
bool _inited;
naContext _context;
naRef _globals,
_string;
SGPropertyNode_ptr _cmdArg;
std::unique_ptr<simgear::BufferedLogCallback> _log;
typedef std::map<std::string, NasalCommand*> NasalCommandDict;
NasalCommandDict _commands;
naRef _wrappedNodeFunc;
// track NasalTimer instances (created via settimer() call) -
// this allows us to clean these up on shutdown
std::vector<NasalTimer*> _nasalTimers;
// NasalTimer is a friend to invoke handleTimer and do the actual
// dispatch of the settimer-d callback
friend NasalTimer;
void handleTimer(NasalTimer* t);
// track persistent timers. These are owned from the Nasal side, so we
// only track a non-owning reference here.
std::vector<TimerObj*> _persistentTimers;
friend TimerObj;
void addPersistentTimer(TimerObj* pto);
void removePersistentTimer(TimerObj* obj);
static void logNasalStack(naContext context, string_list& stack);
};
#if 0
class FGNasalScript
{
public:
~FGNasalScript() { _nas->gcRelease(_gcKey); }
bool call() {
naRef n = naNil();
naCall(_nas->_context, _code, 0, &n, naNil(), naNil());
return naGetError(_nas->_context) == 0;
}
FGNasalSys* sys() const { return _nas; }
private:
friend class FGNasalSys;
naRef _code;
int _gcKey;
FGNasalSys* _nas;
};
#endif
#endif // __NASALSYS_HXX

View File

@@ -0,0 +1,97 @@
// Copyright (C) 2013 James Turner//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef __NASALSYS_PRIVATE_HXX
#define __NASALSYS_PRIVATE_HXX
#include <simgear/props/props.hxx>
#include <simgear/nasal/nasal.h>
#include <simgear/xml/easyxml.hxx>
/**
@breif wrapper for naEqual which recursively checks vec/hash equality
Probably not very performant.
*/
int nasalStructEqual(naContext ctx, naRef a, naRef b);
class FGNasalListener : public SGPropertyChangeListener {
public:
FGNasalListener(SGPropertyNode* node, naRef code, FGNasalSys* nasal,
int key, int id, int init, int type);
virtual ~FGNasalListener();
virtual void valueChanged(SGPropertyNode* node);
virtual void childAdded(SGPropertyNode* parent, SGPropertyNode* child);
virtual void childRemoved(SGPropertyNode* parent, SGPropertyNode* child);
private:
bool changed(SGPropertyNode* node);
void call(SGPropertyNode* which, naRef mode);
friend class FGNasalSys;
SGPropertyNode_ptr _node;
naRef _code;
int _gcKey;
int _id;
FGNasalSys* _nas;
int _init;
int _type;
unsigned int _active;
bool _dead;
long _last_int;
double _last_float;
std::string _last_string;
};
class NasalXMLVisitor : public XMLVisitor {
public:
NasalXMLVisitor(naContext c, int argc, naRef* args);
virtual ~NasalXMLVisitor() { naFreeContext(_c); }
virtual void startElement(const char* tag, const XMLAttributes& a);
virtual void endElement(const char* tag);
virtual void data(const char* str, int len);
virtual void pi(const char* target, const char* data);
private:
void call(naRef func, int num, naRef a = naNil(), naRef b = naNil());
naRef make_string(const char* s, int n = -1);
naContext _c;
naRef _start_element, _end_element, _data, _pi;
};
//
// See the implementation of the settimer() extension function for
// more notes.
//
struct NasalTimer
{
NasalTimer(naRef handler, FGNasalSys* sys);
void timerExpired();
~NasalTimer();
naRef handler;
int gcKey = 0;
FGNasalSys* nasal = nullptr;
};
// declare the interface to the unit-testing module
naRef initNasalUnitTestCppUnit(naRef globals, naContext c);
naRef initNasalUnitTestInSim(naRef globals, naContext c);
#endif // of __NASALSYS_PRIVATE_HXX

View File

@@ -0,0 +1,310 @@
// Unit-test API for nasal
//
// There are two versions of this module, and we load one or the other
// depending on if we're running the test_suite (using CppUnit) or
// the normal simulator. The logic is that aircraft-developers and
// people hacking Nasal likely don't have a way to run the test-suite,
// whereas core-developers and Jenksin want a way to run all tests
// through the standard CppUnit mechanim. So we have a consistent
// Nasal API, but different implement in fgfs_test_suite vs
// normal fgfs executable.
//
// Copyright (C) 2020 James Turner
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include <Scripting/NasalUnitTesting.hxx>
#include <Main/globals.hxx>
#include <Main/util.hxx>
#include <Scripting/NasalSys.hxx>
#include <Scripting/NasalSys_private.hxx>
#include <Main/fg_commands.hxx>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_dir.hxx>
struct ActiveTest
{
bool failure = false;
std::string failureMessage;
std::string failureFileName;
int failLineNumber;
};
static std::unique_ptr<ActiveTest> static_activeTest;
static naRef f_assert(const nasal::CallContext& ctx )
{
bool pass = ctx.requireArg<bool>(0);
auto msg = ctx.getArg<string>(1);
if (!pass) {
if (!static_activeTest) {
ctx.runtimeError("No active test in progress");
}
if (static_activeTest->failure) {
ctx.runtimeError("Active test already failed");
}
static_activeTest->failure = true;
static_activeTest->failureMessage = msg;
static_activeTest->failureFileName = ctx.from_nasal<string>(naGetSourceFile(ctx.c_ctx(), 0));
static_activeTest->failLineNumber = naGetLine(ctx.c_ctx(), 0);
ctx.runtimeError("Test assert failed");
}
return naNil();
}
static naRef f_fail(const nasal::CallContext& ctx )
{
auto msg = ctx.getArg<string>(0);
if (!static_activeTest) {
ctx.runtimeError("No active test in progress");
}
if (static_activeTest->failure) {
ctx.runtimeError("Active test already failed");
}
static_activeTest->failure = true;
static_activeTest->failureMessage = msg;
static_activeTest->failureFileName = ctx.from_nasal<string>(naGetSourceFile(ctx.c_ctx(), 0));
static_activeTest->failLineNumber = naGetLine(ctx.c_ctx(), 0);
ctx.runtimeError("Test failed");
return naNil();
}
static naRef f_assert_equal(const nasal::CallContext& ctx )
{
naRef argA = ctx.requireArg<naRef>(0);
naRef argB = ctx.requireArg<naRef>(1);
auto msg = ctx.getArg<string>(2, "assert_equal failed");
bool same = nasalStructEqual(ctx.c_ctx(), argA, argB);
if (!same) {
string aStr = ctx.from_nasal<string>(argA);
string bStr = ctx.from_nasal<string>(argB);
msg += "; expected:" + aStr + ", actual:" + bStr;
static_activeTest->failure = true;
static_activeTest->failureMessage = msg;
static_activeTest->failureFileName = ctx.from_nasal<string>(naGetSourceFile(ctx.c_ctx(), 0));
static_activeTest->failLineNumber = naGetLine(ctx.c_ctx(), 0);
ctx.runtimeError(msg.c_str());
}
return naNil();
}
static naRef f_assert_doubles_equal(const nasal::CallContext& ctx )
{
double argA = ctx.requireArg<double>(0);
double argB = ctx.requireArg<double>(1);
double tolerance = ctx.requireArg<double>(2);
auto msg = ctx.getArg<string>(3, "assert_doubles_equal failed");
const bool same = fabs(argA - argB) < tolerance;
if (!same) {
msg += "; expected:" + std::to_string(argA) + ", actual:" + std::to_string(argB);
static_activeTest->failure = true;
static_activeTest->failureMessage = msg;
static_activeTest->failureFileName = ctx.from_nasal<string>(naGetSourceFile(ctx.c_ctx(), 0));
static_activeTest->failLineNumber = naGetLine(ctx.c_ctx(), 0);
ctx.runtimeError(msg.c_str());
}
return naNil();
}
static naRef f_equal(const nasal::CallContext& ctx)
{
naRef argA = ctx.requireArg<naRef>(0);
naRef argB = ctx.requireArg<naRef>(1);
bool same = nasalStructEqual(ctx.c_ctx(), argA, argB);
return naNum(same);
}
//------------------------------------------------------------------------------
// commands
bool command_executeNasalTest(const SGPropertyNode *arg, SGPropertyNode * root)
{
SGPath p = SGPath::fromUtf8(arg->getStringValue("path"));
if (p.isRelative()) {
for (auto dp : globals->get_data_paths("Nasal")) {
SGPath absPath = dp / p.utf8Str();
if (absPath.exists()) {
p = absPath;
break;
}
}
}
if (!p.exists() || !p.isFile() || (p.lower_extension() != "nut")) {
SG_LOG(SG_NASAL, SG_DEV_ALERT, "not a Nasal test file:" << p);
return false;
}
return executeNasalTest(p);
}
bool command_executeNasalTestDir(const SGPropertyNode *arg, SGPropertyNode * root)
{
SGPath p = SGPath::fromUtf8(arg->getStringValue("path"));
if (!p.exists() || !p.isDir()) {
SG_LOG(SG_NASAL, SG_DEV_ALERT, "no such directory:" << p);
return false;
}
executeNasalTestsInDir(p);
return true;
}
//------------------------------------------------------------------------------
naRef initNasalUnitTestInSim(naRef nasalGlobals, naContext c)
{
nasal::Hash globals_module(nasalGlobals, c),
unitTest = globals_module.createHash("unitTest");
unitTest.set("assert", f_assert);
unitTest.set("fail", f_fail);
unitTest.set("assert_equal", f_assert_equal);
unitTest.set("assert_doubles_equal", f_assert_doubles_equal);
unitTest.set("equal", f_equal);
globals->get_commands()->addCommand("nasal-test", &command_executeNasalTest);
globals->get_commands()->addCommand("nasal-test-dir", &command_executeNasalTestDir);
return naNil();
}
void executeNasalTestsInDir(const SGPath& path)
{
simgear::Dir d(path);
for (const auto& testFile : d.children(simgear::Dir::TYPE_FILE, "*.nut")) {
SG_LOG(SG_NASAL, SG_INFO, "Processing test file " << testFile);
} // of test files iteration
}
// variant on FGNasalSys parse,
static naRef parseTestFile(naContext ctx, const char* filename,
const char* buf, int len,
std::string& errors)
{
int errLine = -1;
naRef srcfile = naNewString(ctx);
naStr_fromdata(srcfile, (char*)filename, strlen(filename));
naRef code = naParseCode(ctx, srcfile, 1, (char*)buf, len, &errLine);
if(naIsNil(code)) {
std::ostringstream errorMessageStream;
errorMessageStream << "Nasal Test parse error: " << naGetError(ctx) <<
" in "<< filename <<", line " << errLine;
errors = errorMessageStream.str();
SG_LOG(SG_NASAL, SG_DEV_ALERT, errors);
return naNil();
}
const auto nasalSys = globals->get_subsystem<FGNasalSys>();
return naBindFunction(ctx, code, nasalSys->nasalGlobals());
}
bool executeNasalTest(const SGPath& path)
{
naContext ctx = naNewContext();
const auto nasalSys = globals->get_subsystem<FGNasalSys>();
sg_ifstream file_in(path);
const auto source = file_in.read_all();
string errors;
string fileName = path.utf8Str();
naRef code = parseTestFile(ctx, fileName.c_str(),
source.c_str(),
source.size(), errors);
if(naIsNil(code)) {
naFreeContext(ctx);
return false;
}
// create test context
auto localNS = nasalSys->getGlobals().createHash("_test_" + path.utf8Str());
nasalSys->callWithContext(ctx, code, 0, 0, localNS.get_naRef());
auto setUpFunc = localNS.get("setUp");
auto tearDown = localNS.get("tearDown");
for (const auto value : localNS) {
if (value.getKey().find("test_") == 0) {
static_activeTest.reset(new ActiveTest);
if (naIsFunc(setUpFunc)) {
nasalSys->callWithContext(ctx, setUpFunc, 0, nullptr ,localNS.get_naRef());
}
const auto testName = value.getKey();
auto testFunc = value.getValue<naRef>();
if (!naIsFunc(testFunc)) {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "Skipping non-function test member:" << testName);
continue;
}
nasalSys->callWithContext(ctx, testFunc, 0, nullptr, localNS.get_naRef());
if (static_activeTest->failure) {
SG_LOG(SG_NASAL, SG_ALERT, testName << ": Test failure:" << static_activeTest->failureMessage << "\n\tat: " << static_activeTest->failureFileName << ": " << static_activeTest->failLineNumber);
} else {
SG_LOG(SG_NASAL, SG_ALERT, testName << ": Test passed");
}
if (naIsFunc(tearDown)) {
nasalSys->callWithContext(ctx, tearDown, 0, nullptr ,localNS.get_naRef());
}
static_activeTest.reset();
}
}
// remvoe test hash/namespace
naFreeContext(ctx);
return true;
}
void shutdownNasalUnitTestInSim()
{
globals->get_commands()->removeCommand("nasal-test");
globals->get_commands()->removeCommand("nasal-test-dir");
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <Scripting/NasalSys.hxx>
class SGPath;
naRef initNasalUnitTestInSim(naRef globals, naContext c);
void shutdownNasalUnitTestInSim();
void executeNasalTestsInDir(const SGPath& path);
bool executeNasalTest(const SGPath& path);

View File

@@ -0,0 +1,952 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstring>
#include <simgear/nasal/nasal.h>
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>
#include <Main/globals.hxx>
#include "NasalSys.hxx"
using namespace std;
// Implementation of a Nasal wrapper for the SGPropertyNode class,
// using the Nasal "ghost" (er... Garbage collection Handle for
// OutSide Thingy) facility.
//
// Note that these functions appear in Nasal with prepended
// underscores. They work on the low-level "ghost" objects and aren't
// intended for use from user code, but from Nasal code you will find
// in props.nas. That is where the Nasal props.Node class is defined,
// which provides a saner interface along the lines of SGPropertyNode.
static void propNodeGhostDestroy(void* ghost)
{
SGPropertyNode* prop = static_cast<SGPropertyNode*>(ghost);
if (!SGPropertyNode::put(prop)) delete prop;
}
naGhostType PropNodeGhostType = { propNodeGhostDestroy, "prop", nullptr, nullptr };
naRef propNodeGhostCreate(naContext c, SGPropertyNode* ghost)
{
if(!ghost) return naNil();
SGPropertyNode::get(ghost);
return naNewGhost(c, &PropNodeGhostType, ghost);
}
naRef FGNasalSys::propNodeGhost(SGPropertyNode* handle)
{
return propNodeGhostCreate(_context, handle);
}
SGPropertyNode* ghostToPropNode(naRef ref)
{
if (!naIsGhost(ref) || (naGhost_type(ref) != &PropNodeGhostType))
return NULL;
return static_cast<SGPropertyNode*>(naGhost_ptr(ref));
}
#define NASTR(s) s ? naStr_fromdata(naNewString(c),(char*)(s),strlen(s)) : naNil()
//
// Standard header for the extension functions. It turns the "ghost"
// found in arg[0] into a SGPropertyNode_ptr, and then "unwraps" the
// vector found in the second argument into a normal-looking args
// array. This allows the Nasal handlers to do things like:
// Node.getChild = func { _getChild(me.ghost, arg) }
//
#define NODENOARG() \
if(argc < 2 || !naIsGhost(args[0]) || \
naGhost_type(args[0]) != &PropNodeGhostType) \
naRuntimeError(c, "bad argument to props function"); \
SGPropertyNode_ptr node = static_cast<SGPropertyNode*>(naGhost_ptr(args[0]));
#define NODEARG() \
NODENOARG(); \
naRef argv = args[1]
//
// Pops the first argument as a relative path if the first condition
// is true (e.g. argc > 1 for getAttribute) and if it is a string.
// If the second confition is true, then another is popped to specify
// if the node should be created (i.e. like the second argument to
// getNode())
//
// Note that this makes the function return nil if the node doesn't
// exist, so all functions with a relative_path parameter will
// return nil if the specified node does not exist.
//
#define MOVETARGET(cond1, create) \
if(cond1) { \
naRef name = naVec_get(argv, 0); \
if(naIsString(name)) { \
try { \
node = node->getNode(naStr_data(name), create); \
} catch(const string& err) { \
naRuntimeError(c, (char *)err.c_str()); \
return naNil(); \
} \
if(!node) return naNil(); \
naVec_removefirst(argv); /* pop only if we were successful */ \
} \
}
// Get the type of a property (returns a string).
// Forms:
// props.Node.getType(string relative_path);
// props.Node.getType();
static naRef f_getType(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 0, false);
const char* t = "unknown";
switch(node->getType()) {
case props::NONE: t = "NONE"; break;
case props::ALIAS: t = "ALIAS"; break;
case props::BOOL: t = "BOOL"; break;
case props::INT: t = "INT"; break;
case props::LONG: t = "LONG"; break;
case props::FLOAT: t = "FLOAT"; break;
case props::DOUBLE: t = "DOUBLE"; break;
case props::STRING: t = "STRING"; break;
case props::UNSPECIFIED: t = "UNSPECIFIED"; break;
case props::VEC3D: t = "VEC3D"; break;
case props::VEC4D: t = "VEC4D"; break;
case props::EXTENDED: t = "EXTENDED"; break; // shouldn't happen
}
return NASTR(t);
}
// Check if type of a property is numeric (returns 0 or 1).
// Forms:
// props.Node.isNumeric(string relative_path);
// props.Node.isNumeric();
static naRef f_isNumeric(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 0, false);
switch(node->getType()) {
case props::INT:
case props::LONG:
case props::FLOAT:
case props::DOUBLE: return naNum(true);
default:
break;
}
return naNum(false);
}
// Check if type of a property is integer (returns 0 or 1).
// Forms:
// props.Node.isInt(string relative_path);
// props.Node.isInt();
static naRef f_isInt(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 0, false);
if ((node->getType() == props::INT) || (node->getType() == props::LONG)) {
return naNum(true);
}
return naNum(false);
}
// Get an attribute of a property by name (returns true/false).
// Forms:
// props.Node.getType(string relative_path,
// string attribute_name);
// props.Node.getType(string attribute_name);
static naRef f_getAttribute(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
if(naVec_size(argv) == 0) return naNum(unsigned(node->getAttributes()));
MOVETARGET(naVec_size(argv) > 1, false);
naRef val = naVec_get(argv, 0);
const char *a = naStr_data(val);
SGPropertyNode::Attribute attr;
if(!a) a = "";
if(!strcmp(a, "last")) return naNum(SGPropertyNode::LAST_USED_ATTRIBUTE);
else if(!strcmp(a, "children")) return naNum(node->nChildren());
else if(!strcmp(a, "listeners")) return naNum(node->nListeners());
// Number of references without instance used in this function
else if(!strcmp(a, "references")) return naNum(node.getNumRefs() - 1);
else if(!strcmp(a, "tied")) return naNum(node->isTied());
else if(!strcmp(a, "alias")) return naNum(node->isAlias());
else if(!strcmp(a, "readable")) attr = SGPropertyNode::READ;
else if(!strcmp(a, "writable")) attr = SGPropertyNode::WRITE;
else if(!strcmp(a, "archive")) attr = SGPropertyNode::ARCHIVE;
else if(!strcmp(a, "trace-read")) attr = SGPropertyNode::TRACE_READ;
else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
else if(!strcmp(a, "preserve")) attr = SGPropertyNode::PRESERVE;
else if(!strcmp(a, "protected")) attr = SGPropertyNode::PROTECTED;
else if(!strcmp(a, "listener-safe")) attr = SGPropertyNode::LISTENER_SAFE;
else if(!strcmp(a, "value-changed-up")) attr = SGPropertyNode::VALUE_CHANGED_UP;
else if(!strcmp(a, "value-changed-down")) attr = SGPropertyNode::VALUE_CHANGED_DOWN;
else {
naRuntimeError(c, "props.getAttribute() with invalid attribute");
return naNil();
}
return naNum(node->getAttribute(attr));
}
// Set an attribute by name and boolean value or raw (bitmasked) number.
// Forms:
// props.Node.setAttribute(string relative_path,
// string attribute_name,
// bool value);
// props.Node.setAttribute(string attribute_name,
// bool value);
// props.Node.setArtribute(int attributes);
static naRef f_setAttribute(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
if (node->getAttribute(SGPropertyNode::PROTECTED)) {
naRuntimeError(c, "props.setAttribute() called on protected property %s",
node->getPath().c_str());
return naNil();
}
MOVETARGET(naVec_size(argv) > 2, false);
naRef val = naVec_get(argv, 0);
if(naVec_size(argv) == 1 && naIsNum(val)) {
naRef ret = naNum(node->getAttributes());
// prevent Nasal modifying PROTECTED
int attrs = static_cast<int>(val.num) & (~SGPropertyNode::PROTECTED);
node->setAttributes(attrs);
return ret;
}
SGPropertyNode::Attribute attr;
const char *a = naStr_data(val);
if(!a) a = "";
if(!strcmp(a, "readable")) attr = SGPropertyNode::READ;
else if(!strcmp(a, "writable")) attr = SGPropertyNode::WRITE;
else if(!strcmp(a, "archive")) attr = SGPropertyNode::ARCHIVE;
else if(!strcmp(a, "trace-read")) attr = SGPropertyNode::TRACE_READ;
else if(!strcmp(a, "trace-write")) attr = SGPropertyNode::TRACE_WRITE;
else if(!strcmp(a, "userarchive")) attr = SGPropertyNode::USERARCHIVE;
else if(!strcmp(a, "preserve")) attr = SGPropertyNode::PRESERVE;
// explicitly don't allow "protected" to be modified here
else {
naRuntimeError(c, "props.setAttribute() with invalid attribute");
return naNil();
}
naRef ret = naNum(node->getAttribute(attr));
node->setAttribute(attr, naTrue(naVec_get(argv, 1)) ? true : false);
return ret;
}
// Get the simple name of this node.
// Forms:
// props.Node.getName();
static naRef f_getName(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
return NASTR(node->getNameString().c_str());
}
// Get the index of this node.
// Forms:
// props.Node.getIndex();
static naRef f_getIndex(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
return naNum(node->getIndex());
}
// Check if other_node refers to the same as this node.
// Forms:
// props.Node.equals(other_node);
static naRef f_equals(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef rhs = naVec_get(argv, 0);
if( !naIsGhost(rhs) || naGhost_type(rhs) != &PropNodeGhostType )
return naNum(false);
SGPropertyNode* node_rhs = static_cast<SGPropertyNode*>(naGhost_ptr(rhs));
return naNum(node.ptr() == node_rhs);
}
template<typename T>
naRef makeVectorFromVec(naContext c, const T& vec)
{
const int num_components
= sizeof(vec.data()) / sizeof(typename T::value_type);
naRef vector = naNewVector(c);
naVec_setsize(c, vector, num_components);
for (int i = 0; i < num_components; ++i)
naVec_set(vector, i, naNum(vec[i]));
return vector;
}
// Get the value of a node, with or without a relative path.
// Forms:
// props.Node.getValue(string relative_path);
// props.Node.getValue();
static naRef f_getValue(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 0, false);
return FGNasalSys::getPropertyValue(c, node);
}
template<typename T>
T makeVecFromVector(naRef vector)
{
T vec;
const int num_components
= sizeof(vec.data()) / sizeof(typename T::value_type);
int size = naVec_size(vector);
for (int i = 0; i < num_components && i < size; ++i) {
naRef element = naVec_get(vector, i);
naRef n = naNumValue(element);
if (!naIsNil(n))
vec[i] = n.num;
}
return vec;
}
static std::string s_val_description(naRef val)
{
std::ostringstream message;
if (naIsNil(val)) message << "nil";
else if (naIsNum(val)) message << "num:" << naNumValue(val).num;
else if (naIsString(val)) message << "string:" << naStr_data(val);
else if (naIsScalar(val)) message << "scalar";
else if (naIsVector(val)) message << "vector";
else if (naIsHash(val)) message << "hash";
else if (naIsFunc(val)) message << "func";
else if (naIsCode(val)) message << "code";
else if (naIsCCode(val)) message << "ccode";
else if (naIsGhost(val)) message << "ghost";
else message << "?";
return message.str();
}
// Helper function to set the value of a node; returns true if it succeeded or
// false if it failed. <val> can be a string, number, or a
// vector or numbers (for SGVec3D/4D types).
static naRef f_setValueHelper(naContext c, SGPropertyNode_ptr node, naRef val) {
bool result = false;
if(naIsString(val)) {
result = node->setStringValue(naStr_data(val));
} else if(naIsVector(val)) {
if(naVec_size(val) == 3)
result = node->setValue(makeVecFromVector<SGVec3d>(val));
else if(naVec_size(val) == 4)
result = node->setValue(makeVecFromVector<SGVec4d>(val));
else
naRuntimeError(c, "props.setValue() vector value has wrong size");
} else if(naIsNum(val)) {
double d = naNumValue(val).num;
if (SGMisc<double>::isNaN(d)) {
naRuntimeError(c, "props.setValue() passed a NaN");
}
result = node->setDoubleValue(d);
} else {
naRuntimeError(c, "props.setValue() called with unsupported value %s", s_val_description(val).c_str());
}
return naNum(result);
}
// Set the value of a node; returns true if it succeeded or
// false if it failed. <val> can be a string, number, or a
// vector or numbers (for SGVec3D/4D types).
// Forms:
// props.Node.setValue(string relative_path,
// val);
// props.Node.setValue(val);
static naRef f_setValue(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
MOVETARGET(naVec_size(argv) > 1, true);
naRef val = naVec_get(argv, 0);
return f_setValueHelper(c, node, val);
}
static naRef f_setIntValue(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
MOVETARGET(naVec_size(argv) > 1, true);
// Original code:
// int iv = (int)naNumValue(naVec_get(argv, 0)).num;
// Junk to pacify the gcc-2.95.3 optimizer:
naRef tmp0 = naVec_get(argv, 0);
naRef tmp1 = naNumValue(tmp0);
if(naIsNil(tmp1))
naRuntimeError(c, "props.setIntValue() with non-number");
double tmp2 = tmp1.num;
int iv = (int)tmp2;
return naNum(node->setIntValue(iv));
}
static naRef f_setBoolValue(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
MOVETARGET(naVec_size(argv) > 1, true);
naRef val = naVec_get(argv, 0);
return naNum(node->setBoolValue(naTrue(val) ? true : false));
}
static naRef f_toggleBoolValue(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 0, false);
if (node->getType() != props::BOOL) {
naRuntimeError(c, "props.toggleBoolValue() on non-bool prop");
}
const auto val = node->getBoolValue();
return naNum(node->setBoolValue(val ? false : true));
}
static naRef f_setDoubleValue(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
MOVETARGET(naVec_size(argv) > 1, true);
naRef r = naNumValue(naVec_get(argv, 0));
if (naIsNil(r))
naRuntimeError(c, "props.setDoubleValue() with non-number");
if (SGMisc<double>::isNaN(r.num)) {
naRuntimeError(c, "props.setDoubleValue() passed a NaN");
}
return naNum(node->setDoubleValue(r.num));
}
static naRef f_adjustValue(naContext c, naRef me, int argc, naRef* args)
{
using namespace simgear;
NODEARG();
MOVETARGET(naVec_size(argv) > 1, false);
naRef r = naNumValue(naVec_get(argv, 0));
if (naIsNil(r))
naRuntimeError(c, "props.adjustValue() with non-number");
if (SGMisc<double>::isNaN(r.num)) {
naRuntimeError(c, "props.adjustValue() passed a NaN");
}
switch(node->getType()) {
case props::BOOL:
case props::INT:
case props::LONG:
case props::FLOAT:
case props::DOUBLE:
// fall through
break;
default:
naRuntimeError(c, "props.adjustValue() called on non-numeric type");
return naNil();
}
const auto dv = node->getDoubleValue();
return naNum(node->setDoubleValue(dv + r.num));
}
// Forward declaration
static naRef f_setChildrenHelper(naContext c, SGPropertyNode_ptr node, char* name, naRef val);
static naRef f_setValuesHelper(naContext c, SGPropertyNode_ptr node, naRef hash)
{
if (!naIsHash(hash)) {
naRuntimeError(c, "props.setValues() with non-hash");
}
naRef keyvec = naNewVector(c);
naHash_keys(keyvec, hash);
naRef ret;
for (int i = 0; i < naVec_size(keyvec); i++) {
naRef key = naVec_get(keyvec, i);
if (! naIsScalar(key)) {
naRuntimeError(c, "props.setValues() with non-scalar key value");
}
char* keystr = naStr_data(naStringValue(c, key));
ret = f_setChildrenHelper(c, node, keystr, naHash_cget(hash, keystr));
}
return ret;
}
static naRef f_setValues(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
MOVETARGET(naVec_size(argv) > 1, true);
naRef val = naVec_get(argv, 0);
return f_setValuesHelper(c, node, val);
}
static naRef f_setChildrenHelper(naContext c, SGPropertyNode_ptr node, char* name, naRef val)
{
naRef ret;
try {
SGPropertyNode_ptr subnode = node->getNode(name, true);
if (naIsScalar(val)) {
ret = f_setValueHelper(c, subnode, val);
} else if (naIsHash(val)) {
ret = f_setValuesHelper(c, subnode, val);
} else if (naIsVector(val)) {
char nameBuf[1024];
for (int i = 0; i < naVec_size(val); i++) {
const auto len = ::snprintf(nameBuf, sizeof(nameBuf), "%s[%i]", name, i);
if ((len < 0) || (len >= (int) sizeof(nameBuf))) {
naRuntimeError(c, "Failed to create buffer for property name in setChildren");
}
ret = f_setChildrenHelper(c, node, nameBuf, naVec_get(val, i));
}
} else if (naIsNil(val)) {
// Nil value OK - no-op
} else {
// We have an error, but throwing a runtime error will prevent certain things from
// working (such as the pilot list)
// The nasal version would fail silently with invalid data - a runtime error will dump the stack and
// stop execution.
// Overall to be safer the new method should be functionally equivalent to keep compatibility.
//
//REMOVED: naRuntimeError(c, "props.setChildren() with unknown type");
}
} catch(const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
return ret;
}
static naRef f_setChildren(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
if(! naIsString(naVec_get(argv, 0))) {
naRuntimeError(c, "props.setChildren() with non-string first argument");
}
char* name = naStr_data(naVec_get(argv, 0));
naRef val = naVec_get(argv, 1);
return f_setChildrenHelper(c, node, name, val);
}
// Get the parent of this node as a ghost.
// Forms:
// props.Node.getParent();
static naRef f_getParent(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
SGPropertyNode* n = node->getParent();
if(!n) return naNil();
return propNodeGhostCreate(c, n);
}
// Get a child by name and optional index=0, creating if specified (by default it
// does not create it). If the node does not exist and create is false, then it
// returns nil, else it returns a (possibly new) property ghost.
// Forms:
// props.Node.getChild(string relative_path,
// int index=0,
// bool create=false);
static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef child = naVec_get(argv, 0);
if(!naIsString(child)) return naNil();
naRef idx = naNumValue(naVec_get(argv, 1));
bool create = naTrue(naVec_get(argv, 2)) != 0;
SGPropertyNode* n;
try {
if(naIsNil(idx)) {
n = node->getChild(naStr_data(child), create);
} else {
n = node->getChild(naStr_data(child), (int)idx.num, create);
}
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
if(!n) return naNil();
return propNodeGhostCreate(c, n);
}
// Get all children with a specified name as a vector of ghosts.
// Forms:
// props.Node.getChildren(string relative_path);
// props.Node.getChildren(); #get all children
static naRef f_getChildren(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef result = naNewVector(c);
if(naIsNil(argv) || naVec_size(argv) == 0) {
// Get all children
for(int i=0; i<node->nChildren(); i++)
naVec_append(result, propNodeGhostCreate(c, node->getChild(i)));
} else {
// Get all children of a specified name
naRef name = naVec_get(argv, 0);
if(!naIsString(name)) return naNil();
try {
vector<SGPropertyNode_ptr> children
= node->getChildren(naStr_data(name));
for(unsigned int i=0; i<children.size(); i++)
naVec_append(result, propNodeGhostCreate(c, children[i]));
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
}
return result;
}
// Append a named child at the first unused index...
// Forms:
// props.Node.addChild(string name,
// int min_index=0,
// bool append=true);
static naRef f_addChild(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef child = naVec_get(argv, 0);
if(!naIsString(child)) return naNil();
naRef ref_min_index = naNumValue(naVec_get(argv, 1));
naRef ref_append = naVec_get(argv, 2);
SGPropertyNode* n;
try
{
int min_index = 0;
if(!naIsNil(ref_min_index))
min_index = ref_min_index.num;
bool append = true;
if(!naIsNil(ref_append))
append = naTrue(ref_append) != 0;
n = node->addChild(naStr_data(child), min_index, append);
}
catch (const string& err)
{
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
return propNodeGhostCreate(c, n);
}
static naRef f_addChildren(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef child = naVec_get(argv, 0);
if(!naIsString(child)) return naNil();
naRef ref_count = naNumValue(naVec_get(argv, 1));
naRef ref_min_index = naNumValue(naVec_get(argv, 2));
naRef ref_append = naVec_get(argv, 3);
try
{
size_t count = 0;
if( !naIsNum(ref_count) )
throw string("props.addChildren() missing number of children");
count = ref_count.num;
int min_index = 0;
if(!naIsNil(ref_min_index))
min_index = ref_min_index.num;
bool append = true;
if(!naIsNil(ref_append))
append = naTrue(ref_append) != 0;
const simgear::PropertyList& nodes =
node->addChildren(naStr_data(child), count, min_index, append);
naRef result = naNewVector(c);
for( size_t i = 0; i < nodes.size(); ++i )
naVec_append(result, propNodeGhostCreate(c, nodes[i]));
return result;
}
catch (const string& err)
{
naRuntimeError(c, (char *)err.c_str());
}
return naNil();
}
// Remove a child by name and index. Returns it as a ghost.
// Forms:
// props.Node.removeChild(string relative_path,
// int index);
static naRef f_removeChild(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef child = naVec_get(argv, 0);
naRef index = naVec_get(argv, 1);
if(!naIsString(child) || !naIsNum(index)) return naNil();
SGPropertyNode_ptr n;
try {
n = node->getChild(naStr_data(child), (int)index.num);
if (n && n->getAttribute(SGPropertyNode::PROTECTED)) {
naRuntimeError(c, "props.Node.removeChild() called on protected child %s of %s",
naStr_data(child), node->getPath().c_str());
return naNil();
}
n = node->removeChild(naStr_data(child), (int)index.num);
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
}
return propNodeGhostCreate(c, n);
}
// Remove all children with specified name. Returns a vector of all the nodes
// removed as ghosts.
// Forms:
// props.Node.removeChildren(string relative_path);
// props.Node.removeChildren(); #remove all children
static naRef f_removeChildren(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef result = naNewVector(c);
if(naIsNil(argv) || naVec_size(argv) == 0) {
// Remove all children
for(int i = node->nChildren() - 1; i >=0; i--) {
SGPropertyNode_ptr n = node->getChild(i);
if (n->getAttribute(SGPropertyNode::PROTECTED)) {
SG_LOG(SG_NASAL, SG_ALERT, "props.Node.removeChildren: node " <<
n->getPath() << " is protected");
continue;
}
node->removeChild(i);
naVec_append(result, propNodeGhostCreate(c, n));
}
} else {
// Remove all children of a specified name
naRef name = naVec_get(argv, 0);
if(!naIsString(name)) return naNil();
try {
auto children = node->getChildren(naStr_data(name));
for (auto cn : children) {
if (cn->getAttribute(SGPropertyNode::PROTECTED)) {
SG_LOG(SG_NASAL, SG_ALERT, "props.Node.removeChildren: node " <<
cn->getPath() << " is protected");
continue;
}
node->removeChild(cn);
naVec_append(result, propNodeGhostCreate(c, cn));
}
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
}
return result;
}
// Remove all children of a property node.
// Forms:
// props.Node.removeAllChildren();
static naRef f_removeAllChildren(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
node->removeAllChildren();
return propNodeGhostCreate(c, node);
}
// Alias this property to another one; returns 1 on success or 0 on failure
// (only applicable to tied properties).
// Forms:
// props.Node.alias(string global_path);
// props.Node.alias(prop_ghost node);
// props.Node.alias(props.Node node); #added by props.nas
static naRef f_alias(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
if (node->getAttribute(SGPropertyNode::PROTECTED)) {
naRuntimeError(c, "props.Node.alias() called on protected property %s",
node->getPath().c_str());
return naNil();
}
SGPropertyNode* al;
naRef prop = naVec_get(argv, 0);
try {
if(naIsString(prop)) al = globals->get_props()->getNode(naStr_data(prop), true);
else if(naIsGhost(prop)) al = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
else throw string("props.alias() with bad argument");
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
return naNum(node->alias(al));
}
// Un-alias this property. Returns 1 on success or 0 on failure (only
// applicable to tied properties).
// Forms:
// props.Node.unalias();
static naRef f_unalias(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
return naNum(node->unalias());
}
// Get the alias of this node as a ghost.
// Forms:
// props.Node.getAliasTarget();
static naRef f_getAliasTarget(naContext c, naRef me, int argc, naRef* args)
{
NODENOARG();
return propNodeGhostCreate(c, node->getAliasTarget());
}
// Get a relative node. Returns nil if it does not exist and create is false,
// or a ghost object otherwise (wrapped into a props.Node object by props.nas).
// Forms:
// props.Node.getNode(string relative_path,
// bool create=false);
static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
{
NODEARG();
naRef path = naVec_get(argv, 0);
bool create = naTrue(naVec_get(argv, 1)) != 0;
if(!naIsString(path)) return naNil();
SGPropertyNode* n;
try {
n = node->getNode(naStr_data(path), create);
} catch (const string& err) {
naRuntimeError(c, (char *)err.c_str());
return naNil();
}
return propNodeGhostCreate(c, n);
}
// Create a new property node.
// Forms:
// props.Node.new();
static naRef f_new(naContext c, naRef me, int argc, naRef* args)
{
return propNodeGhostCreate(c, new SGPropertyNode());
}
// Get the global root node (cached by props.nas so that it does
// not require a function call).
// Forms:
// props._globals()
// props.globals
static naRef f_globals(naContext c, naRef me, int argc, naRef* args)
{
return propNodeGhostCreate(c, globals->get_props());
}
static struct {
naCFunction func;
const char* name;
} propfuncs[] = {
{ f_getType, "_getType" },
{ f_getAttribute, "_getAttribute" },
{ f_setAttribute, "_setAttribute" },
{ f_getName, "_getName" },
{ f_getIndex, "_getIndex" },
{ f_equals, "_equals" },
{ f_getValue, "_getValue" },
{ f_setValue, "_setValue" },
{ f_setValues, "_setValues" },
{ f_setIntValue, "_setIntValue" },
{ f_setBoolValue, "_setBoolValue" },
{ f_toggleBoolValue, "_toggleBoolValue" },
{ f_setDoubleValue, "_setDoubleValue" },
{ f_getParent, "_getParent" },
{ f_getChild, "_getChild" },
{ f_getChildren, "_getChildren" },
{ f_addChild, "_addChild" },
{ f_addChildren, "_addChildren" },
{ f_removeChild, "_removeChild" },
{ f_removeChildren, "_removeChildren" },
{ f_removeAllChildren, "_removeAllChildren" },
{ f_setChildren, "_setChildren" },
{ f_alias, "_alias" },
{ f_unalias, "_unalias" },
{ f_getAliasTarget, "_getAliasTarget" },
{ f_getNode, "_getNode" },
{ f_new, "_new" },
{ f_globals, "_globals" },
{ f_isNumeric, "_isNumeric" },
{ f_isInt, "_isInt" },
{ f_adjustValue, "_adjustValue" },
{ 0, 0 }
};
naRef FGNasalSys::genPropsModule()
{
naRef namespc = naNewHash(_context);
for(int i=0; propfuncs[i].name; i++)
hashset(namespc, propfuncs[i].name,
naNewFunc(_context, naNewCCode(_context, propfuncs[i].func)));
return namespc;
}
naRef FGNasalSys::getPropertyValue(naContext c, SGPropertyNode* node)
{
using namespace simgear;
if (!node)
return naNil();
switch(node->getType()) {
case props::BOOL: case props::INT:
case props::LONG: case props::FLOAT:
case props::DOUBLE:
{
double dv = node->getDoubleValue();
if (SGMisc<double>::isNaN(dv)) {
SG_LOG(SG_NASAL, SG_ALERT, "Nasal getValue: property " << node->getPath() << " is NaN");
return naNil();
}
return naNum(dv);
}
case props::STRING:
case props::UNSPECIFIED:
return NASTR(node->getStringValue().c_str());
case props::VEC3D:
return makeVectorFromVec(c, node->getValue<SGVec3d>());
case props::VEC4D:
return makeVectorFromVec(c, node->getValue<SGVec4d>());
default:
return naNil();
}
}