Library globals

Source globals.nas

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
##
# 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 (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(DEV_WARN, "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);