b6da3872f1
This avoids getting confusing errors later on, e.g. see bug 2641.
207 lines
6.3 KiB
Text
207 lines
6.3 KiB
Text
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
#
|
|
# NOTE! This copyright does *not* cover user models that use these Nasal
|
|
# services by normal function calls - this is merely considered normal use
|
|
# of the code, and does *not* fall under the heading of "derived work."
|
|
#
|
|
# Copyright (C) 2003-2008 by andy
|
|
# Copyright (C) 2008-2009 by mfranz
|
|
# Copyright (C) 2013 by janodesbois
|
|
# Copyright (C) 2014 by Anton Gomez Alvedro
|
|
|
|
##
|
|
# Constants.
|
|
#
|
|
var D2R = math.pi / 180; # degree to radian
|
|
var R2D = 180 / math.pi; # radian to degree
|
|
|
|
var FT2M = 0.3048; # feet to meter
|
|
var M2FT = 1 / FT2M;
|
|
var IN2M = FT2M / 12;
|
|
var M2IN = 1 / IN2M;
|
|
var NM2M = 1852; # nautical miles to meter
|
|
var M2NM = 1 / NM2M;
|
|
|
|
var KT2MPS = 0.5144444444; # knots to m/s
|
|
var MPS2KT = 1 / KT2MPS;
|
|
|
|
var FPS2KT = 0.5924838012958964; # fps to knots
|
|
var KT2FPS = 1 / FPS2KT;
|
|
|
|
var LB2KG = 0.45359237; # pounds to kg
|
|
var KG2LB = 1 / LB2KG;
|
|
|
|
var GAL2L = 3.785411784; # US gallons to liter
|
|
var L2GAL = 1 / GAL2L;
|
|
|
|
|
|
# container for local variables, so as not to clutter the global namespace
|
|
var __ = {};
|
|
|
|
##
|
|
# Aborts execution if <condition> evaluates to false.
|
|
# Prints an optional message if present, or just "assertion failed!"
|
|
#
|
|
var assert = func (condition, message=nil) {
|
|
message != nil or (message = "assertion failed!");
|
|
condition or die(message);
|
|
}
|
|
|
|
##
|
|
# Returns true if the first object is an instance of the second
|
|
# (class) object. Example: isa(someObject, props.Node)
|
|
#
|
|
var isa = func(obj, class) {
|
|
if (ishash(obj) and obj["parents"] != nil) {
|
|
foreach(var c; obj.parents) {
|
|
if (c == class or isa(c, class)) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
##
|
|
# Invokes a FlightGear command specified by the first argument. The
|
|
# second argument specifies the property tree to be passed to the
|
|
# command as its argument. It may be either a props.Node object or a
|
|
# string, in which case it specifies a path in the global property
|
|
# tree.
|
|
#
|
|
var fgcommand = func(cmd, node=nil) {
|
|
if (isa(node, props.Node)) node = node._g;
|
|
elsif (ishash(node)) node = props.Node.new(node)._g;
|
|
_fgcommand(cmd, node);
|
|
}
|
|
|
|
##
|
|
# Returns the SGPropertyNode argument to the currently executing
|
|
# function. Wrapper for the internal _cmdarg function that retrieves
|
|
# the ghost handle to the argument and wraps it in a
|
|
# props.Node object.
|
|
#
|
|
var cmdarg = func { props.wrapNode(_cmdarg()) }
|
|
|
|
##
|
|
# Utility. Does what you think it does.
|
|
#
|
|
var abs = func(v) { return v < 0 ? -v : v }
|
|
|
|
##
|
|
# Convenience wrapper for the _interpolate function. Takes a
|
|
# single string or props.Node object in arg[0] indicating a target
|
|
# property, and a variable-length list of time/value pairs. Example:
|
|
#
|
|
# interpolate("/animations/radar/angle",
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0,
|
|
# 180, 1, 360, 1, 0, 0);
|
|
#
|
|
# This will swing the "radar dish" smoothly through 8 revolutions over
|
|
# 16 seconds. Note the use of zero-time interpolation between 360 and
|
|
# 0 to wrap the interpolated value properly.
|
|
#
|
|
var interpolate = func(node, val...) {
|
|
if (isa(node, props.Node))
|
|
node = node._g;
|
|
elsif (!isscalar(node) and !isghost(node))
|
|
die("bad argument to interpolate()");
|
|
_interpolate(node, val);
|
|
}
|
|
|
|
|
|
##
|
|
# Wrapper for the _setlistener function. Takes a property path string
|
|
# or props.Node object in arg[0] indicating the listened to property,
|
|
# a function in arg[1], an optional bool in arg[2], which triggers the
|
|
# function initially if true, and an optional integer in arg[3], which
|
|
# sets the listener's runtime behavior to "only trigger on change" (0),
|
|
# "always trigger on write" (1), and "trigger even when children are
|
|
# written to" (2).
|
|
#
|
|
var setlistener = func(node, fn, init = 0, runtime = 1) {
|
|
if (typeof(fn) != "func") {
|
|
# This avoids unhelpful failures later on when we try to call <fn> - we
|
|
# get an error without a useful backtrace, because property callbacks
|
|
# use a new Nasal context.
|
|
die(sprintf("setlistener() called with non-function; typeof(fn)=%s", typeof(fn)));
|
|
}
|
|
if (isa(node, props.Node)) node = node._g;
|
|
elsif (!isscalar(node) and !isghost(node))
|
|
die("bad argument to setlistener()");
|
|
var id = _setlistener(node, func(chg, lst, mode, is_child) {
|
|
fn(props.wrapNode(chg), props.wrapNode(lst), mode, is_child);
|
|
}, init, runtime);
|
|
|
|
var c = caller();
|
|
if (c != nil) {
|
|
logprint(LOG_DEBUG, "setting listener #",id," in ",c[2],":",c[3]);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
|
|
##
|
|
# Returns true if the symbol name is defined in the caller, or the
|
|
# caller's lexical namespace. (i.e. defined("varname") tells you if
|
|
# you can use varname in an expression without a undefined symbol
|
|
# error.
|
|
#
|
|
var defined = func(sym) {
|
|
if (contains(caller(1)[0], sym)) return 1;
|
|
var fn = caller(1)[1];
|
|
for (var l=0; (var frame = closure(fn, l)) != nil; l+=1)
|
|
if (contains(frame, sym)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
##
|
|
# Returns reference to calling function. This allows a function to
|
|
# reliably call itself from a closure, rather than the global function
|
|
# with the same name.
|
|
#
|
|
var thisfunc = func caller(1)[1];
|
|
|
|
|
|
##
|
|
# Just what it says it is.
|
|
#
|
|
var printf = func print(call(sprintf, arg));
|
|
|
|
|
|
##
|
|
# Returns vector of hash values.
|
|
#
|
|
var values = func(hash) {
|
|
var vec = [];
|
|
foreach(var key; keys(hash)) append(vec, hash[key]);
|
|
return vec;
|
|
}
|
|
|
|
# printlog is depricated, use logprint instead
|
|
|
|
__.dbg_types = { none:0, bulk:1, debug:2, info:3, warn:4, alert:5 };
|
|
var printlog = func(level, msg...) {
|
|
var c = caller();
|
|
logprint(LOG_ALERT, "Deprecated printlog() call from ",c[2]~":"~c[3]~
|
|
", please use logprint instead.");
|
|
logprint([__.dbg_types[level]]~msg);
|
|
}
|
|
|
|
|
|
##
|
|
# Load and execute ~/.fgfs/Nasal/*.nas files in alphabetic order
|
|
# after all $FG_ROOT/Nasal/*.nas files were loaded.
|
|
#
|
|
settimer(func {
|
|
var path = getprop("/sim/fg-home") ~ "/Nasal";
|
|
if((var dir = directory(path)) == nil) return;
|
|
foreach(var file; sort(dir, cmp))
|
|
if(size(file) > 4 and substr(file, -4) == ".nas")
|
|
io.load_nasal(path ~ "/" ~ file, substr(file, 0, size(file) - 4));
|
|
}, 0);
|